一:前言
在多线程编程中,线程池是一种常用的工具,可以有效管理和调度大量的线程任务。然而,在高负载情况下,线程池可能无法接受新的任务,这就需要定义一种拒绝策略来处理这种情况。本文将介绍线程池的拒绝策略,以及如何选择合适的策略来优化处理能力。
二:什么是线程池的拒绝策略?
线程池的拒绝策略是在线程池无法接受新任务时采取的一种处理方式。当线程池中的线程已经达到最大容量并且队列已满时,新的任务将会被拒绝。拒绝策略定义了线程池如何处理这些被拒绝的任务,以避免任务丢失或造成系统崩溃。
三: 拒绝策略的四种类型
Java线程池提供了几种内置的拒绝策略,包括以下几种:
(1) AbortPolicy(默认策略)
该策略直接抛出RejectedExecutionException异常,阻止线程池接受新任务。这是最简单和最直接的拒绝方式,但可能会丢失部分任务。
(2) CallerRunsPolicy
该策略将任务回退给调用者执行,而不是在新的线程中执行。当线程池无法接受新任务时,任务会由提交任务的线程执行。这种策略可能会降低系统的吞吐量,但能够保证任务的执行,避免任务丢失。
(3) DiscardPolicy
该策略直接丢弃无法处理的任务,没有任何异常抛出。这种策略可能会导致部分任务被忽略掉,无法正常执行。
(4) DiscardOldestPolicy
该策略先丢弃队列中等待时间最长的任务,然后尝试将新任务加入队列。这种策略可以保证新任务得到执行,但可能会丢失部分等待时间较长的任务。
四:四种线程池拒绝策略的分析
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS, // 候客区 new LinkedBlockingDeque<>(3), // 默认线程工厂 Executors.defaultThreadFactory(), // 默认的拒绝策略,银行满了还有人进来,不处理,抛出异常 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略,哪来的去哪里 // new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略, 队列满了不会抛出异常 // new ThreadPoolExecutor.DiscardPolicy() // 拒绝策略,队列满了,尝试去和最早的竞争,不会抛出异常 // new ThreadPoolExecutor.DiscardOldestPolicy() );
(1) AbortPolicy(默认策略)
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
A handler for rejected tasks that throws a {@code RejectedExecutionException}.
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
(2) CallerRunsPolicy
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务
(3) DiscardPolicy
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
A handler for rejected tasks that silently discards therejected task.
使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。
(4) DiscardOldestPolicy
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
五:如何选择合适的拒绝策略?
选择适合的拒绝策略取决于具体的业务需求和系统环境:
- 如果系统对任务丢失比较敏感,可以选择CallerRunsPolicy,保证任务得到执行,但要注意这可能会影响系统的吞吐量。
- 如果系统对任务丢失并不敏感,可以选择DiscardPolicy或DiscardOldestPolicy。前者会直接丢弃任务,而后者会丢弃等待时间最长的任务。
- 如果系统对任务的完整性要求较高,可以选择AbortPolicy,并在任务被拒绝时及时处理RejectedExecutionException异常。
此外,也可以根据具体情况实现自定义的拒绝策略。通过实现RejectedExecutionHandler接口,可以定义自己的拒绝策略逻辑,例如将拒绝的任务记录下来,或者将任务重新放入队列等。
六:总结
线程池的拒绝策略是优化处理能力的关键。选择合适的拒绝策略可以避免任务丢失,并根据业务需求和系统环境来权衡吞吐量和任务完整性。了解不同拒绝策略的特点和使用场景,可以帮助我们更好地设计和管理线程池