线程池拒绝策略是设计好的压力信号,仅在队列满且线程数达maximumpoolsize时触发;abortpolicy快速失败抛异常,需配套日志、监控与熔断;callerrunspolicy由调用线程执行任务,易致雪崩;discard类策略静默丢弃,难感知;自定义策略须防阻塞、超时与日志打爆。

线程池任务溢出时,拒绝策略不是故障,而是设计好的压力信号。它只在两个条件同时满足时触发:工作队列已满(且为有界队列),并且当前线程数已达 maximumPoolSize。此时新提交的任务不会自动排队或重试,而是交由 RejectedExecutionHandler 执行预设逻辑——选错策略,轻则丢任务,重则压垮调用方。
AbortPolicy:默认≠安全,是“快速失败”的警报器
抛出 RejectedExecutionException,不处理、不补偿、不静默。它把压力直接暴露给上层,倒逼调用方做决策。
- 适合场景:关键路径任务(如支付回调、订单创建),你已有完善的
try-catch+ 上下文日志 + 监控告警 - 必须补足三件事:捕获异常时记录任务 ID 和类型;打点到监控系统(如
metrics.increment("rejected"));配置熔断或降级兜底 - 典型误用:Web 接口直接抛该异常却不捕获,导致 HTTP 500 暴露给用户,且无日志定位具体哪个请求被拒
CallerRunsPolicy:表面“不丢”,实则反向传导压力
由提交任务的线程(比如 Tomcat 的 worker 线程)同步执行被拒任务。它不新增线程、不丢任务,但会拖慢调用方。
- 适用前提:调用方本身吞吐可控(如定时调度器)、或你明确做了提交限流(如每秒最多 submit 5 次)
- 高风险场景:HTTP 请求线程执行耗时任务 → 响应延迟飙升 → 连接池打满 → 上游超时雪崩
- 建议加防护:提交前先判断
if (!executor.isShutdown()),避免 shutdown 后仍强行塞任务
DiscardPolicy 与 DiscardOldestPolicy:静音丢弃,但丢法不同
两者都不抛异常,但丢弃对象和副作用差异极大:
- DiscardPolicy:直接扔掉当前新任务,零日志、零痕迹。仅适用于完全可丢的流量,如客户端埋点、非关键心跳上报
-
DiscardOldestPolicy:先从队列头
poll()一个最老任务(不管它多重要),再尝试execute(r)当前任务。若队列持续满,可能反复丢掉刚进来的高频任务,而关键任务卡在队尾永远得不到执行 - 共性风险:线上无法感知丢弃行为。若没自行包装策略并加入限流日志(如
logger.warn("discarded: {}", taskId)),等于盲调
自定义策略:自由度高,但三个坑踩中一个就失效
实现 RejectedExecutionHandler 接口看似简单,实际容易引发新瓶颈:
- 禁止在
rejectedExecution()中调用executor.submit()或任何可能阻塞的操作(如远程写 Kafka、DB 插入未设超时),否则可能死锁或线程耗尽 - 若做异步补偿(如发消息重试),补偿逻辑自身必须有超时、重试上限和失败降级(如降级为本地文件暂存)
- 避免高频打印完整堆栈(
e.printStackTrace()),改用带限流的 warn 日志,防止日志系统被打爆










