线程池拒绝策略在两种场景下触发:①线程池RUNNING状态且corePoolSize、workQueue、maximumPoolSize均满;②线程池已shutdown/shutdownNow。

线程池拒绝策略不是配置完就高枕无忧的“装饰项”,而是系统在过载时能否稳住不崩的关键开关。生产环境里,它往往在流量高峰、下游抖动或突发异常时才真正露面——这时候再调策略,已经晚了。
明确触发条件,别等报警才看日志
拒绝策略只在两个确定场景下生效:
• 线程池处于 RUNNING 状态,但 corePoolSize 已满 + workQueue 已满 + maximumPoolSize 已满;
• 或线程池已调用 shutdown() / shutdownNow(),后续任务一律拒绝。
注意:队列没满,哪怕核心线程全忙,也不会触发拒绝;最大线程数没配,等于把“扩容通道”堵死,队列一满立刻进拒绝流程。
四种内置策略不能照搬,得按业务语义选
AbortPolicy(默认):抛 RejectedExecutionException。适合强一致性场景,比如支付扣款、库存预占——必须让上游立刻感知失败,不能静默吞掉。但要确保调用方有 try-catch 和重试/降级逻辑,否则可能炸穿整个链路。
CallerRunsPolicy:由提交线程自己执行任务。本质是反压机制,能自然限流,适合非核心异步操作(如日志上报、埋点采集)。但要注意——如果主线程是 Web 容器线程(如 Tomcat 的 worker 线程),它去同步执行耗时任务,会直接拖慢接口响应,甚至引发请求堆积。
DiscardPolicy:静默丢弃。仅适用于完全可丢失、无状态的任务,例如统计类心跳、低优先级缓存刷新。千万别用在订单创建、消息推送等有业务后果的路径上。
DiscardOldestPolicy:丢队首、留队尾。适合“只关心最新状态”的场景,比如设备状态上报、仪表盘实时指标更新——旧值过期无意义,新值必须进。
自定义策略才是生产标配
真实业务中,这四种策略几乎都不够用。推荐至少做三件事:
• 记录被拒任务关键信息:任务类型、提交时间、线程池名称、当前活跃线程数、队列剩余容量;
• 对接监控告警:拒绝量突增 5 倍或连续 1 分钟 > 0,立即发企业微信/钉钉告警;
• 提供兜底逃生通道:比如将任务序列化后写入本地磁盘或 Kafka 死信 Topic,后续由补偿服务重放。避免“一拒即失”。
示例要点:拒绝方法内不要做耗时操作(如远程调用、DB 写入),防止阻塞 reject 调用栈;优先用异步日志框架(如 Log4j2 AsyncLogger)记日志。
立即学习“Java免费学习笔记(深入)”;
配套动作比策略本身更重要
光选对策略不够,还得配齐观测和预案:
• 在 Prometheus + Grafana 中暴露线程池指标:activeCount、poolSize、queueSize、rejectedTasks;
• 每个线程池命名带业务前缀(如 order-async-pool),方便日志和监控归因;
• 定期压测验证拒绝策略是否真生效(模拟队列打满 + 提交新任务,检查日志/告警/监控是否符合预期);
• 把拒绝率(rejected / submitted)纳入 SLA 监控基线,超过 0.1% 就触发根因分析。


















