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

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
可观测监控 Prometheus 版,每月50GB免费额度
函数计算FC,每月15万CU 3个月
简介: 本文介绍了线程池的四种拒绝策略: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拒绝策略:只要主线程创建的线程池没有关闭,则由主线程来执行任务。这种由主线程来一个一个执行被拒绝新任务,从而实现降低新任务的提交速度。
相关文章
|
28天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
62 7
|
2月前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
35 4
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
40 0
|
2月前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
20天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
102 13
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
28天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
29天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####