Java线程池ThreadPoolExcutor源码解读详解09-4种拒绝策略

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
可观测可视化 Grafana 版,10个用户账号 1个月
性能测试 PTS,5000VUM额度
简介: 本文介绍了线程池的四种拒绝策略:AbortPolicy、DiscardPolicy、DiscardOldestPolicy和CallerRunsPolicy,并通过代码示例展示了它们在任务过多时的不同处理方式。AbortPolicy会抛出异常并停止主线程;DiscardPolicy会默默丢弃新任务;DiscardOldestPolicy会抛弃队列中最旧的任务来接纳新任务;而CallerRunsPolicy则是由调用者线程执行被拒绝的任务,以减缓新任务的提交速度。这四种策略适用于不同的场景,开发者可以根据需求选择合适的策略。

💪🏻 制定明确可量化的目标,坚持默默的做事。





一、继承实现关系图

线程池的四种拒绝策略实现关系图:

image.gif image.png

  • AbortPolicy(默认):直接抛出RejectedExecutionException异常并且阻止主线程正常运行
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
  • CallerRunsPolicy:“调用者运行“一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,让调用者去处理,比如main调用了线程池,线程池处理不了就让main慢慢处理去,从而降低新任务的流量

二、拒绝策略测试示例

// 测试任务类
class Task implements Runnable {
        private String name;
        public Task(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(1100);
                System.out.println("thread name: " + this.name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

image.gif

2.1 AbortPolicy策略

测试代码块:

// ThreadPoolExcecutor类中
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
// 测试类
private void abortPolicy() {
    ThreadFactory factory = r -> new Thread(r, "AbortPolicy ThreadFactory");
    // 创建核心线程和最大线程为2的线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), factory);
    int i = 0;
    int n = 100;
    // 添加100个任务
    while (i++ < n) {
        threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i));
    }
}

image.gif

运行如上代码打印如下:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task w.cx.lrn.java.interview.threadpool.ThreadPolicyTester$Task@4d405ef7 rejected from java.util.concurrent.ThreadPoolExecutor@6193b845[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
  at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
  at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
  at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
  at w.cx.lrn.java.interview.threadpool.ThreadPolicyTester.abortPolicy(ThreadPolicyTester.java:34)
  at w.cx.lrn.java.interview.threadpool.ThreadPolicyTester.main(ThreadPolicyTester.java:18)
thread name: 1
thread name: 2
thread name: 3
thread name: 4

image.gif

说明:

  • 没有指定拒绝策略,默认为AbortPolicy拒绝策略
  • 最大线程数为2,则有两个任务在运行
  • 阻塞队列数为2,则阻塞队列中可添加2个任务
  • 添加第五个任务的时候抛出了 RejectedExecutionException异常并且阻止主线程正常运行(没有再尝试添加第六个线程),所以只打印前4个任务1,2,3和4任务

2.2 DiscardPolicy策略

测试代码块:(添了拒绝策略)

private void discardPolicy() {
    ThreadFactory factory2 = r -> new Thread(r, "DiscardPolicy ThreadFactory");
    // 核心线程数和、最大线程数 和 阻塞队列大小均为2
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.DiscardPolicy());
    int i = 0;
    int n = 10;
    // 添加10个任务
    while (i++ < n) {
        threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i));
        System.out.println("添加了第 " + i + " 个任务!");
    }
}

image.gif

运行如上代码打印如下:

添加了第 1 个任务!
添加了第 2 个任务!
添加了第 3 个任务!
添加了第 4 个任务!
添加了第 5 个任务!
添加了第 6 个任务!
添加了第 7 个任务!
添加了第 8 个任务!
添加了第 9 个任务!
添加了第 10 个任务!
thread name: 1
thread name: 2
thread name: 4
thread name: 3

image.gif

说明:

  • 指定拒绝策略为DiscardPolicy
  • 最大线程数为2,则有两个任务在运行
  • 阻塞队列数为2,则阻塞队列中可添加2个任务
  • 添加第5个到第10个任务都不予任何处理也不抛出异常,所以只打印前4个任务1,2,3和4任务

2.3 DiscardOldestPolicy策略

测试代码块:(添了DiscardOldestPolicy拒绝策略)

private void discardOldestPolicy() {
    ThreadFactory factory2 = r -> new Thread(r, "DiscardOldestPolicy ThreadFactory");
    // 核心线程数、最大线程数 和 阻塞队列大小均为2
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
            2,
            0,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.DiscardOldestPolicy());
    int i = 0;
    int n = 10;
    // 添加10个任务
    while (i++ < n) {
        threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i));
        System.out.println("添加了第 " + i + " 个任务!");
    }
}

image.gif

运行如上代码打印如下:

添加了第 1 个任务!
添加了第 2 个任务!
添加了第 3 个任务!
添加了第 4 个任务!
添加了第 5 个任务!
添加了第 6 个任务!
添加了第 7 个任务!
添加了第 8 个任务!
添加了第 9 个任务!
添加了第 10 个任务!
thread name: 1
thread name: 2
thread name: 9
thread name: 10

image.gif

说明:

  • 指定拒绝策略为DiscardOldestPolicy
  • 最大线程数为2,则有两个任务在运行
  • 阻塞队列数为2,则阻塞队列中可添加2个任务
  • 拒绝策略为DiscardOldestPolicy,添加第5个时,则抛弃第3个任务添加第5个任务,添加第6个任务时,抛弃第4个任务添加第6个任务,依次类推最后阻塞队列中存放的任务为第9个和第10个,所以最后只打印前2个任务1,2和9、10任务

2.4 CallerRunsPolicy策略

测试代码块:(添了CallerRunsPolicy拒绝策略)

private void callerRunsPolicy() {
    ThreadFactory factory2 = r -> new Thread(r, "TestThreadPool");
    // 核心线程数、最大线程数 和 阻塞队列大小均为2
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
            2,
            0,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.CallerRunsPolicy());
    int i = 0;
    int n = 10;
    // 添加10个任务
    while (i++ < n) {
        threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i));
        System.out.println("添加了第 " + i + " 个任务!");
    }
}

image.gif

为了方便辨别是主线程执行还是子线程执行,测试任务类的打印修改成如下:

 

System.out.println("thread name: " + Thread.currentThread().getName() + " " + this.name);

image.gif

运行如上代码打印如下:

添加了第 1 个任务!
添加了第 2 个任务!
添加了第 3 个任务!
添加了第 4 个任务!
thread name: callerRunsPolicy  2
thread name: callerRunsPolicy  1
thread name: main 5
添加了第 5 个任务!
添加了第 6 个任务!
添加了第 7 个任务!
thread name: callerRunsPolicy  3
thread name: main 8
添加了第 8 个任务!
添加了第 9 个任务!
thread name: callerRunsPolicy  4
thread name: callerRunsPolicy  6
thread name: main 10
thread name: callerRunsPolicy  7
添加了第 10 个任务!
thread name: callerRunsPolicy  9

image.gif

说明:(多线程环境下运行,每次打印都不一样)

  • 指定拒绝策略为CallerRunsPolicy
  • 最大线程数为2,则有两个任务在运行
  • 阻塞队列数为2,则阻塞队列中可添加2个任务
  • 10任务都打印出来了,被拒绝的第5、8 和 10任务是由主线程执行的,拒绝策略源码如下。就是说添加新任务被拒绝后,只要主线程创建的线程池没有关闭,由主线程来执行被拒绝的新任务。只有线程池关闭了,被拒绝的任务才被丢弃且不抛异常。

拒绝策略源码:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

image.gif

三、总结及应用场景

  • AbortPolicy拒绝策略:新任务不能再添加的时候,会直接抛出异常及时反馈程序运行状态,在一些比较重要的业务中,使用此策略在不能再处理更多的并发量的时候,就能及时的通过异常发现问题。
  • DiscardPolicy拒绝策略:在不能再添加新任务时此策略直接丢弃任务。比如在一些无关紧要的任务可用此策略。
  • DiscardOldestPolicy拒绝策略:新任务替换老任务。应用于任务分配有一定的容忍性的情况,弃用最老的任务以容纳新的任务,一般是可以接受的情况下。
  • CallerRunsPolicy拒绝策略:只要主线程创建的线程池没有关闭,则由主线程来执行任务。这种由主线程来一个一个执行被拒绝新任务,从而实现降低新任务的提交速度。
相关文章
|
8天前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
29 5
|
2月前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
58 0
|
21天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
175 37
|
6天前
|
传感器 监控 数据可视化
【Java】智慧工地解决方案源码和所需关键技术
智慧工地解决方案是一种新的工程全生命周期管理理念。它通过使用各种传感器、数传终端等物联网手段获取工程施工过程信息,并上传到云平台,以保障数据安全。
30 7
|
25天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
25天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
16天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
26天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
6天前
|
Java 数据中心 微服务
Java高级知识:线程池隔离与信号量隔离的实战应用
在Java并发编程中,线程池隔离与信号量隔离是两种常用的资源隔离技术,它们在提高系统稳定性、防止系统过载方面发挥着重要作用。
6 0
下一篇
无影云桌面