线程池四种拒绝策略(ThreadPoolExecutor 内置)
📅 2026/7/5 6:42:48
👁️ 阅读次数
📝 编程学习
线程池四种拒绝策略(ThreadPoolExecutor 内置)
当满足线程池饱和条件时触发拒绝:
- 核心线程全部繁忙
- 阻塞队列已满
- 最大线程数已达到上限
此时新提交任务会走拒绝策略,JDK 内置 4 种实现。
前置基础构造示意
// 核心线程2,最大5,队列容量3,空闲线程存活0sThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,0L,TimeUnit.MILLISECONDS,newArrayBlockingQueue<>(3));// 设置拒绝策略executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());饱和临界点:同时提交超过maxPoolSize + queueSize = 5+3=8个任务,第9个开始触发拒绝策略。
1. AbortPolicy(默认策略:直接抛出异常)
代码
executor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());行为
直接抛出RejectedExecutionException,中断当前任务提交,不执行任务。
示例
for(inti=0;i<10;i++){executor.submit(()->{try{Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}});}提交第9个任务直接抛异常,程序不捕获会中断业务流程。
使用场景
- 任务不允许丢失,且调用方必须感知失败、主动降级重试;
- 同步阻塞业务、支付、核心数据处理,不允许静默丢失任务;
- 小型同步接口,任务量可控,异常统一捕获做告警/重试。
2. DiscardPolicy(丢弃当前新任务,无任何提示)
代码
executor.setRejectedExecutionHandler(newThreadPoolExecutor.DiscardPolicy());行为
默默丢掉新来的任务,不抛异常、无日志、无告警,调用方无感知。
风险
任务永久丢失,业务数据缺失很难排查。
使用场景
- 非核心、可丢弃的统计上报、埋点日志、实时监控采集;
- 允许数据丢失、对一致性无要求的边缘辅助任务。
3. DiscardOldestPolicy(丢弃队列最老未执行任务,执行新任务)
代码
executor.setRejectedExecutionHandler(newThreadPoolExecutor.DiscardOldestPolicy());行为
取出阻塞队列头部排队最久的任务丢弃,再尝试提交当前新任务。
使用场景
- 实时数据、最新状态覆盖旧数据:如设备实时状态上报、行情推送;
- 旧任务无保留价值,最新数据才有效,不需要历史排队任务。
不适用场景
订单、入库、消息消费等顺序/历史任务不能丢的业务。
4. CallerRunsPolicy(调用者线程执行,你提问中的策略)
代码(你给出的)
executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());行为
不丢任务、不抛异常,把当前任务交给提交任务的主线程同步执行。
举例子:主线程调用executor.submit(),池子满了,任务直接在主线程跑,阻塞主线程直到任务完成。
效果
- 不会丢失任何任务;
- 提交线程被阻塞,天然限流;
- 减缓任务提交速度,给线程池留出时间处理堆积。
示例演示
ThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,0L,TimeUnit.MILLISECONDS,newArrayBlockingQueue<>(3),newThreadPoolExecutor.CallerRunsPolicy());// 模拟10个长耗时任务for(inti=0;i<10;i++){inttaskId=i;System.out.println("提交任务:"+taskId+" 提交线程:"+Thread.currentThread().getName());executor.submit(()->{System.out.println("执行任务:"+taskId+" 执行线程:"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){}});}输出现象:前8个由池内线程执行,第9、10个任务打印执行线程为main,主线程同步执行。
使用场景
- 流量削峰、不允许丢任务:文件同步、数据库批量入库、定时采集任务(你项目采集接口非常适配);
- 无独立重试机制、任务丢失会造成业务数据缺失;
- 接口流量突增时,自动降级为调用线程执行,起到限流保护线程池;
- Spring 内置定时池、普通业务异步线程池常用该策略。
缺点
高并发下主线程阻塞,接口响应时间变长,吞吐量下降。
四种策略对比表
| 策略 | 处理逻辑 | 是否丢任务 | 是否抛异常 | 典型适用场景 |
|---|---|---|---|---|
| AbortPolicy | 抛异常拒绝 | 丢当前任务 | 是 | 核心交易、需感知失败的同步业务 |
| DiscardPolicy | 静默丢弃新任务 | 丢当前任务 | 否 | 非核心埋点、监控日志 |
| DiscardOldestPolicy | 丢弃队列最老任务 | 丢旧任务 | 否 | 实时状态、行情推送 |
| CallerRunsPolicy | 调用线程同步执行 | 不丢失 | 否 | 数据采集、入库、定时任务、流量削峰 |
生产使用建议(结合你项目采集定时任务)
你的业务是http采集定时任务,推荐CallerRunsPolicy:
- 采集数据不能丢,丢弃会造成数据断层;
- 池子打满时主线程同步执行,自动限流,避免无限创建任务OOM;
- 无需额外编写重试、丢失补偿逻辑。
拓展:自定义拒绝策略(生产常用兜底)
内置策略无法满足告警需求时自定义,打印日志+推送告警:
publicclassCustomRejectHandlerimplementsRejectedExecutionHandler{@OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){log.error("线程池饱和,任务被拒绝,核心:{},最大:{},队列容量:{}",executor.getCorePoolSize(),executor.getMaximumPoolSize(),executor.getQueue().size());// 推送钉钉/短信告警// 可选:本地持久化任务,后续补偿thrownewRuntimeException("任务提交失败,线程池已满");}}
编程学习
技术分享
实战经验