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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 本文介绍了线程池的四种拒绝策略: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拒绝策略:只要主线程创建的线程池没有关闭,则由主线程来执行任务。这种由主线程来一个一个执行被拒绝新任务,从而实现降低新任务的提交速度。
相关文章
|
3天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
13 5
|
3天前
|
监控 安全 NoSQL
采用java+springboot+vue.js+uniapp开发的一整套云MES系统源码 MES制造管理系统源码
MES系统是一套具备实时管理能力,建立一个全面的、集成的、稳定的制造物流质量控制体系;对生产线、工艺、人员、品质、效率等多方位的监控、分析、改进,满足精细化、透明化、自动化、实时化、数据化、一体化管理,实现企业柔性化制造管理。
24 3
|
3天前
|
存储 Java
【Java】实现一个简单的线程池
,如果被消耗完了就说明在规定时间内获取不到任务,直接return结束线程。
11 0
|
3天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第18天】在Java并发编程中,锁是一种常用的同步机制,用于保护共享资源的访问。然而,不当的锁使用可能导致性能问题和死锁风险。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁分离和读写锁等技术,以提高并发程序的性能和可靠性。
|
4天前
|
存储 Java
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究
|
4天前
|
数据采集 监控 安全
java数字工厂MES系统全套源码Java+idea+springboot专业为企业提供智能制造MES解决方案
"MES" 指的是制造执行系统(Manufacturing Execution System)。MES在制造业中扮演着至关重要的角色,它是位于企业资源计划(ERP)系统和车间控制系统之间的系统,用于实时收集、管理、分析和报告与制造过程相关的数据。
11 0
|
4天前
|
移动开发 监控 供应链
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
一开始接触MES系统,很多人会和博主一样,对MES细节的应用不了解,这样很正常,因为MES系统相对于其他系统来讲应用比较多!
16 1
JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(app、小程序、H5、台后管理端)
|
4天前
|
Java 编译器
Java 并发编程中的锁优化策略
【5月更文挑战第17天】在 Java 并发编程中,锁是一种常见的同步机制,用于保护共享资源的访问。然而,不当使用锁可能导致性能问题和死锁风险。本文将探讨 Java 中的锁优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。
|
4天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第17天】在Java并发编程中,锁是一种常见的同步机制,用于保护共享资源。然而,使用不当的锁可能导致性能下降和死锁等问题。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁排序等方法,以提高程序的性能和可靠性。
|
4天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf