rejectedexecutionhandler是设计好的压力信号,仅在队列满且线程数达maximumpoolsize时触发;abortpolicy快速失败需配套监控告警,callerrunspolicy易引发雪崩,discard类策略静默丢弃难感知,自定义策略须避免阻塞和日志爆炸。

线程池任务溢出时,RejectedExecutionHandler不是故障补救措施,而是设计好的压力信号——它只在两个条件同时满足时触发:工作队列已满(且为有界队列),当前活跃线程数已达 maximumPoolSize。此时新任务无法排队、不会重试,直接交由拒绝策略处理。选错策略,轻则静默丢任务,重则反向压垮调用方。
AbortPolicy:快速失败,但必须配套可观测性
默认策略,抛出 RejectedExecutionException,不补偿、不静默。它的价值在于“立刻暴露问题”,倒逼上游做响应决策。
- 适合关键路径任务,比如支付回调、订单落库、风控同步校验
- 必须捕获异常并记录任务标识(如 traceId、taskId)、类型和提交时间
- 需打点监控指标(如
rejected_task_count),并联动告警 - Web 场景中若未 catch,会直接返回 HTTP 500,且无上下文日志,极难定位
CallerRunsPolicy:调用线程兜底,但风险隐蔽
由提交任务的线程(如 Tomcat 的 worker 线程)同步执行被拒任务。表面“不丢任务”,实则把压力传导回上游。
- 适用于提交方本身可控的场景,例如定时调度器每秒最多 submit 3 次
- HTTP 接口大量使用该策略易引发雪崩:响应延迟上升 → 连接池耗尽 → 上游超时重试 → 压力倍增
- 务必在 execute 前加判断:
if (!executor.isShutdown()),避免 shutdown 后仍强行执行 - 不建议在高吞吐网关或 API 层直接启用
DiscardPolicy 与 DiscardOldestPolicy:静默丢弃,行为差异大
两者都不抛异常,但丢法不同,后果迥异:
- DiscardPolicy:直接丢弃当前新任务,零日志、零痕迹。仅适用于完全可丢的流量,如前端埋点、非关键心跳
- DiscardOldestPolicy:先从队列头 poll 一个最老任务(不管其优先级或重要性),再尝试 execute 当前任务。若队列持续满,高频任务反复被丢,而关键任务卡在队尾永远得不到执行
- 共性缺陷:线上无法感知丢弃行为。若未自行包装策略并加入限流日志(如
logger.warn("discarded: {}", taskId)),等于盲调
自定义策略:自由度高,但三个关键约束不能破
实现 RejectedExecutionHandler 接口看似简单,实际极易引入新瓶颈:
- 禁止在
rejectedExecution方法中调用executor.submit()或任何可能阻塞的操作,否则可能死锁或线程耗尽 - 若做异步补偿(如发 Kafka、写 DB),补偿逻辑自身必须带超时和降级,否则拒绝处理反而成了系统瓶颈
- 避免在拒绝逻辑中打印完整堆栈(
e.printStackTrace()),高频拒绝时 I/O 会打爆磁盘或日志系统;改用带限流的 warn 日志










