• Java ThreadPoolExecutor的拒绝策略


    背景

    线程池的技术在项目中使用广泛,线程池提供了四种拒绝策略,大家是否了解这四种拒绝的策略呢?本文将详细的讲解ThreadPoolExecutor的四种拒绝策略,以及相关的注意事项。

    线程池基本原理

    线程池的原理如下图:

    说明:

    • 当前运行的线程少于corePoolSize,则创建新线程来执行任务。
    • 运行的线程等于或多于corePoolSize,则将任务添加到队列中。
    • 当任务队列已满,则在非corePool中创建新的线程来处理任务。
    • 创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

    线程池拒绝策略

    线程池为我们提供了四种拒绝策略分别是:CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy

    AbortPolicy

    ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy直接抛出异常,具体实现如下

    1. public static class AbortPolicy implements RejectedExecutionHandler {
    2. public AbortPolicy() { }
    3. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    4. throw new RejectedExecutionException("Task " + r.toString() +
    5. " rejected from " +
    6. e.toString());
    7. }
    8. }
    9. 复制代码

    说明:这种策略非常简单粗暴,直接抛出RejectedExecutionException异常,也不会执行后续的任务。

    示例说明:

    1. public class ThreadPoolTest
    2. {
    3. public static void main(String[] args)
    4. {
    5. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    6. 2,
    7. 5,
    8. 10,
    9. TimeUnit.MICROSECONDS,
    10. new LinkedBlockingDeque<>(1),
    11. new ThreadPoolExecutor.AbortPolicy());
    12. //异步执行
    13. for(int i=0; i<10;i++)
    14. {
    15. System.out.println("添加第"+i+"个任务");
    16. threadPoolExecutor.execute(new TestThread("线程"+i));
    17. }
    18. }
    19. }
    20. public class TestThread implements Runnable
    21. {
    22. private String name;
    23. public TestThread(String name){
    24. this.name=name;
    25. }
    26. @Override
    27. public void run()
    28. {
    29. try
    30. {
    31. Thread.sleep(1000);
    32. }
    33. catch (InterruptedException e)
    34. {
    35. e.printStackTrace();
    36. }
    37. System.out.println("thread name:"+Thread.currentThread().getName()+",执行:"+name);
    38. }
    39. }
    40. 复制代码

    执行结果:

    1. Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.skywares.fw.juc.thread.TestThread@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
    2. at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    3. at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    4. at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    5. at com.skywares.fw.juc.thread.ThreadPoolTest.main(ThreadPoolTest.java:26)
    6. thread name:pool-1-thread-5,执行:线程5
    7. thread name:pool-1-thread-2,执行:线程1
    8. thread name:pool-1-thread-4,执行:线程4
    9. thread name:pool-1-thread-3,执行:线程3
    10. thread name:pool-1-thread-1,执行:线程0
    11. thread name:pool-1-thread-5,执行:线程2
    12. 复制代码

    从执行结果我们得知,采用AbortPolicy策略当任务执行到第七个任务时会直接报错,导致后续的业务逻辑不会执行。

    CallerRunsPolicy

    CallerRunsPolicy在任务被拒绝添加后,会用调用execute函数的上层线程去执行被拒绝的任务。

    相关示例

    1. public class ThreadPoolTest
    2. {
    3. public static void main(String[] args)
    4. {
    5. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    6. 2,
    7. 5,
    8. 10,
    9. TimeUnit.MICROSECONDS,
    10. new LinkedBlockingDeque<>(1),
    11. new ThreadPoolExecutor.CallerRunsPolicy());
    12. //异步执行
    13. for(int i=0; i<10;i++)
    14. {
    15. System.out.println("添加第"+i+"个任务");
    16. threadPoolExecutor.execute(new TestThread("线程"+i));
    17. }
    18. }
    19. }
    20. 复制代码

    执行结果:

    1. 添加第0个任务
    2. 添加第1个任务
    3. 添加第2个任务
    4. 添加第3个任务
    5. 添加第4个任务
    6. 添加第5个任务
    7. 添加第6个任务
    8. thread name:main,执行:线程6
    9. thread name:pool-1-thread-3,执行:线程3
    10. thread name:pool-1-thread-1,执行:线程0
    11. thread name:pool-1-thread-4,执行:线程4
    12. thread name:pool-1-thread-2,执行:线程1
    13. thread name:pool-1-thread-5,执行:线程5
    14. 添加第7个任务
    15. 添加第8个任务
    16. thread name:main,执行:线程8
    17. thread name:pool-1-thread-1,执行:线程7
    18. thread name:pool-1-thread-3,执行:线程2
    19. 添加第9个任务
    20. thread name:pool-1-thread-1,执行:线程9
    21. 复制代码

    从执行的结果我们可以得知,当执行到第7个任务时,由于线程池拒绝策略,此任务由主线程来执行,当线程池有空闲时,才继续执行其他的任务。所以此策略可能会阻塞主线程。

    DiscardPolicy

    这种拒绝策略比较简单,线程池拒绝的任务直接抛弃,不会抛异常也不会执行

    示例

    修改上述的代码,将拒绝策略修改为DiscardPolicy

    1. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    2. 2,
    3. 5,
    4. 10,
    5. TimeUnit.MICROSECONDS,
    6. new LinkedBlockingDeque<>(1),
    7. new ThreadPoolExecutor.CallerRunsPolicy());
    8. 复制代码

    执行结果

    1. invoke dealStock success
    2. goodsId:手机
    3. thread name:pool-1-thread-1,执行:线程0
    4. thread name:pool-1-thread-4,执行:线程4
    5. thread name:pool-1-thread-5,执行:线程5
    6. thread name:pool-1-thread-3,执行:线程3
    7. thread name:pool-1-thread-2,执行:线程1
    8. thread name:pool-1-thread-1,执行:线程2
    9. 复制代码

    从执行的结果来看只执行了6个任务,其他的任务都被抛弃了。

    DiscardOldestPolicy

    DiscardOldestPolicy 当任务拒绝添加时,会抛弃任务队列中最先加入队列的任务,再把新任务添加进去。

    示例说明

    1. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    2. 1,
    3. 2,
    4. 10,
    5. TimeUnit.MICROSECONDS,
    6. new LinkedBlockingDeque<>(2),
    7. new ThreadPoolExecutor.CallerRunsPolicy());
    8. 复制代码

    执行结果:

    1. 添加第0个任务
    2. 添加第1个任务
    3. 添加第2个任务
    4. 添加第3个任务
    5. 添加第4个任务
    6. 添加第5个任务
    7. invoke dealStock success
    8. goodsId:手机
    9. thread name:pool-1-thread-2,执行:线程3
    10. thread name:pool-1-thread-1,执行:线程0
    11. thread name:pool-1-thread-1,执行:线程2
    12. thread name:pool-1-thread-2,执行:线程1
    13. 复制代码

    自定义拒绝策略

    当线程池提供的拒绝策略无法满足要求时,我们可以采用自定义的拒绝策略,只需要实现RejectedExecutionHandler接口即可

    1. public class CustRejectedExecutionHandler implements RejectedExecutionHandler
    2. {
    3. @Override
    4. public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
    5. {
    6. new Thread(r,"线程:"+new Random().nextInt(10)).start();
    7. }
    8. }
    9. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    10. 1,
    11. 2,
    12. 10,
    13. TimeUnit.MICROSECONDS,
    14. new LinkedBlockingDeque<>(2),
    15. new CustRejectedExecutionHandler());
    16. 复制代码

    执行结果:

    1. thread name:客户线程:6,执行:线程5
    2. thread name:pool-1-thread-1,执行:线程0
    3. thread name:客户线程:8,执行:线程4
    4. thread name:pool-1-thread-2,执行:线程3
    5. thread name:pool-1-thread-1,执行:线程1
    6. thread name:pool-1-thread-2,执行:线程2
    7. 复制代码

    从执行的结果来看,被拒绝的任务都在客户的新线程中执行。

    小结

    • AbortPolicy:直接抛出异常,后续的任务不会执行
    • CallerRunsPolicy:子任务执行的时间过长,可能会阻塞主线程。
    • DiscardPolicy:不抛异常,任务直接丢弃
    • DiscardOldestPolicy;丢弃最先加入队列的任务

    总结

    本文对于线程的池的几种策略进行详细的讲解,在实际的生产中需要集合相关的场景来选择合适的拒绝策略,如有疑问,请随时反馈。

  • 相关阅读:
    Qt通过QMetaObject创建实例
    CLIP论文解读
    Docker学习(一)
    (文献随笔)肿瘤浸润的活化B细胞抑制结直肠癌的肝转移(Cell Report, 2022年8月30日)
    在Ubuntu20.04中安装中文输入法
    又一单SoC行泊一体域控发布!这家厂商将性能和成本“卷”到极致
    极大似然估计和交叉熵
    C++类和对象经典oj
    2042. 检查句子中的数字是否递增-力扣双百代码-设置前置数据
    jQuery
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126603378