2020-06-04:线程池拒绝策略分别使用在什么场景

本文深入解读福哥口诀法中的线程池拒绝策略,包括中止策略、丢弃策略、弃老策略及调用者运行策略,探讨其功能与适用场景,如关键业务、无关紧要任务、发布消息及不允许失败场景,帮助读者理解并合理选择拒绝策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

福哥口诀法:拒中丢老调(线程池拒绝策略:中止策略、丢弃策略、弃老策略、调用者运行策略)

简单回答:
中止策略:无特殊场景。
丢弃策略:无关紧要的任务(博客阅读量)。
弃老策略:发布消息。
调用者运行策略:不允许失败场景(对性能要求不高、并发量较小)。

详细回答:
1.AbortPolicy中止策略:丢弃任务并抛出RejectedExecutionException异常。

这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

功能:当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程.
使用场景:这个就没有特殊的场景了,但是有一点要正确处理抛出的异常。ThreadPoolExecutor中默认的策略就是AbortPolicy,ExecutorService接口的系列ThreadPoolExecutor因为都没有显示的设置拒绝策略,所以默认的都是这个。但是请注意,ExecutorService中的线程池实例队列都是无界的,也就是说把内存撑爆了都不会触发拒绝策略。当自己自定义线程池实例时,使用这个策略一定要处理好触发策略时抛的异常,因为他会打断当前的执行流程。

2.DiscardPolicy丢弃策略:ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。

使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。

功能:直接静悄悄的丢弃这个任务,不触发任何动作。
使用场景:如果你提交的任务无关紧要,你就可以使用它 。因为它就是个空实现,会悄无声息的吞噬你的的任务。所以这个策略基本上不用了。

3.DiscardOldestPolicy弃老策略:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

功能:如果线程池未关闭,就弹出队列头部的元素,然后尝试执行
使用场景:这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,想到的场景就是,发布消息和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。因为队列中还有可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。

4.CallerRunsPolicy调用者运行策略:由调用线程处理该任务。
功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。
使用场景:一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。

5.dubbo中的线程拒绝策略。
当dubbo的工作线程触发了线程拒绝后,主要做了三个事情,原则就是尽量让使用者清楚触发线程拒绝策略的真实原因。
(1)输出了一条警告级别的日志,日志内容为线程池的详细设置参数,以及线程池当前的状态,还有当前拒绝任务的一些详细信息。可以说,这条日志,使用dubbo的有过生产运维经验的或多或少是见过的,这个日志简直就是日志打印的典范,其他的日志打印的典范还有spring。得益于这么详细的日志,可以很容易定位到问题所在。
(2)输出当前线程堆栈详情,这个太有用了,当你通过上面的日志信息还不能定位问题时,案发现场的dump线程上下文信息就是你发现问题的救命稻草。
(3)继续抛出拒绝执行异常,使本次任务失败,这个继承了JDK默认拒绝策略的特性。

6.Netty中的线程池拒绝策略。
Netty中的实现很像JDK中的CallerRunsPolicy,舍不得丢弃任务。不同的是,CallerRunsPolicy是直接在调用者线程执行的任务。而 Netty是新建了一个线程来处理的。所以,Netty的实现相较于调用者执行策略的使用面就可以扩展到支持高效率高性能的场景了。但是也要注意一点,Netty的实现里,在创建线程时未做任何的判断约束,也就是说只要系统还有资源就会创建新的线程来处理,直到new不出新的线程了,才会抛创建线程失败的异常。

7.activeMq中的线程池拒绝策略。
activeMq中的策略属于最大努力执行任务型,当触发拒绝策略时,在尝试一分钟的时间重新将任务塞进任务队列,当一分钟超时还没成功时,就抛出异常。

8.pinpoint中的线程池拒绝策略。
pinpoint的拒绝策略实现很有特点,和其他的实现都不同。他定义了一个拒绝策略链,包装了一个拒绝策略列表,当触发拒绝策略时,会将策略链中的rejectedExecution依次执行一遍。

评论

### 不同线程池拒绝策略的适用业务场景及示例 #### 1. **AbortPolicy** AbortPolicy 是最简单也是最常见的拒绝策略之一。当线程池无法接受新的任务时,它会直接抛出 `RejectedExecutionException` 异常。这种策略适合那些对任务丢失零容忍的应用场景[^4]。 ##### 示例 ```java ExecutorService executor = new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy() ); try { for (int i = 0; i < 10; i++) { final int taskNumber = i; executor.submit(() -> System.out.println("Executing Task " + taskNumber)); } } catch (RejectedExecutionException e) { System.err.println("Task rejected due to thread pool overload."); } finally { executor.shutdown(); } ``` 在这个例子中,如果尝试向已满的工作队列提交第7个任务,将会触发 `RejectedExecutionException` 并打印错误消息。 --- #### 2. **CallerRunsPolicy** CallerRunsPolicy 的特点是让调用者(即提交任务的那个线程)亲自执行该任务。这有助于减慢任务提交的速度,从而给系统更多时间去消化积压的任务。适用于不希望丢弃任务,并且希望通过降低任务提交速度来缓解系统压力的情况[^4]。 ##### 示例 ```java ExecutorService executor = new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.CallerRunsPolicy() ); for (int i = 0; i < 10; i++) { final int taskNumber = i; executor.submit(() -> System.out.println(Thread.currentThread().getName() + ": Executing Task " + taskNumber)); } executor.shutdown(); ``` 在此代码片段中,一旦核心线程和阻塞队列都满了,后续的任务将由主线程自行处理,而不是交给线程池中的某个工作线程。 --- #### 3. **DiscardPolicy** DiscardPolicy 只是默默地丢弃被拒绝的任务而不做任何提示或反馈。因此,它非常适合那种即使偶尔丢失部分任务也不会带来严重后果的服务环境,例如某些非关键的日志记录操作[^4]。 ##### 示例 ```java ExecutorService executor = new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.DiscardPolicy() ); for (int i = 0; i < 10; i++) { final int taskNumber = i; executor.submit(() -> System.out.println("Executing Task " + taskNumber)); } executor.shutdown(); ``` 假如超过容量限制的新任务到来,它们会被静默抛弃,没有任何警告信息输出至控制台或其他地方。 --- #### 4. **DiscardOldestPolicy** DiscardOldestPolicy 则会选择放弃等待最长的一个尚未得到服务的机会转而接纳最新到达的任务进入排队序列当中。这样的做法特别有利于维护最新的状态表示形式,尤其是一些需要频繁刷新显示界面之类的功能模块里面非常实用。 ##### 示例 ```java ExecutorService executor = new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.DiscardOldestPolicy() ); for (int i = 0; i < 10; i++) { final int taskNumber = i; executor.submit(() -> System.out.println("Executing Task " + taskNumber)); } executor.shutdown(); ``` 在这种设定下,每当新增加进来一个超出承载能力范围之外的任务实例时候,就会把之前已经等候许久却还没有轮到自己开始运行的那个最早的候选对象踢出去腾位置出来供后来者占据。 --- #### 自定义拒绝策略 除了上述四种标准选项以外,还可以依据具体的业务需求来自行定制个性化的解决方案。只需继承 `RejectedExecutionHandler` 接口并重写其中的方法即可达成目标[^3]。 ##### 示例 ```java public class LoggingRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.printf("%s has been rejected at %s%n", r.toString(), LocalDateTime.now()); } } // 使用自定义拒绝处理器初始化线程池... ThreadPoolExecutor customPool = new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new LoggingRejectedExecutionHandler()); customPool.execute(() -> {}); customPool.shutdown(); ``` 在这里展示了一个简单的日志型拒绝处理器的例子,每遇到一次拒收情况都会自动记录下来相应的细节描述便于后期追踪审查之用。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福大大架构师每日一题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值