ThreadPoolExecutor拒绝策略仅在三条件同时满足时触发:线程数达maximumPoolSize、有界队列已满、线程池处于RUNNING状态;AbortPolicy抛异常适用于强一致场景,CallerRunsPolicy由调用线程执行实现反压,DiscardPolicy静默丢弃适合可丢失任务,DiscardOldestPolicy丢弃队列最老任务优先保障新任务时效性。

分析 ThreadPoolExecutor 的拒绝策略,关键不是背定义,而是理解它在什么条件下被触发、每种策略如何改变任务流向,以及业务对“丢失”“延迟”“一致性”的真实容忍度。
拒绝策略的触发时机必须明确
很多问题源于误判触发条件。拒绝策略只在以下三者同时成立时才会生效:
- 所有核心线程正在执行任务
- 任务队列已满(注意:是“有界队列”才可能满;无界队列如 LinkedBlockingQueue 默认容量 Integer.MAX_VALUE,几乎不会触发)
- 当前线程数已达 maximumPoolSize,且无法再创建新线程
只要其中任一条件不满足,任务就会被接纳——或入队,或扩容线程。因此,分析前先确认你用的是 有界队列(如 ArrayBlockingQueue),否则 DiscardPolicy、DiscardOldestPolicy 等策略根本不会起作用。
四种内置策略的行为与风险点
每种策略本质是不同“降级路径”,需对应具体执行后果:
- AbortPolicy(默认):抛 RejectedExecutionException。适合支付、订单创建等强一致性场景——宁可失败也不接受不可控延迟。但若调用方未捕获异常,会导致上游服务崩溃。
- CallerRunsPolicy:由提交线程(比如 Tomcat 的 worker 线程)直接执行任务。它不丢任务,但会拖慢调用方——相当于把压力反传给上游。适用于轻量计算、允许短暂阻塞 HTTP 请求的后台作业(如用户行为埋点聚合)。
- DiscardPolicy:静默丢弃。适合日志上报、指标采集等“丢了也不影响主流程”的任务。风险是完全无感知,需配合监控(如统计 rejected 次数)才能发现过载。
- DiscardOldestPolicy:踢掉队列头的任务,再尝试提交当前任务。适合实时性要求高的场景(如传感器数据刷新),但要注意:被踢任务可能已部分执行(若 Runnable 不是纯函数式),存在状态不一致风险。
选型要结合线程池整体配置
拒绝策略不能孤立看待。例如:
- 用 CallerRunsPolicy 时,若 corePoolSize 设得太小、队列又太短,会导致大量请求在主线程串行执行,TPS 断崖下跌——这不是策略问题,是容量规划失当。
- 用 DiscardOldestPolicy 配合 SynchronousQueue(容量为 0),等于每次都在“踢旧换新”,实际效果接近 CallerRunsPolicy,但少了调用线程执行的可控性。
- 高吞吐 IO 密集型任务(如文件上传回调),建议搭配较大队列 + AbortPolicy + 告警,避免因瞬时毛刺触发拒绝而掩盖真实瓶颈。
自定义策略要解决实际问题
直接继承 RejectedExecutionHandler 接口即可,重点不是“记录日志”,而是闭环处理:
- 写入本地磁盘队列(如 RocksDB),后续重试——适合不允许丢失但可延迟的场景(如消息推送)
- 发 Slack/钉钉告警 + 上报 Prometheus counter ——用于容量预警
- 降级为异步写 Kafka,交由下游消费重试——解耦主链路与补偿逻辑
真正有效的自定义策略,一定包含可观测性(打标、计数)和可恢复性(持久化、重试入口),而不是仅打印一行 System.err。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











