拒绝策略在核心线程满、队列满、最大线程数达上限且线程池未关闭时生效;四种策略分别实现抛异常(AbortPolicy)、调用者执行(CallerRunsPolicy)、静默丢弃(DiscardPolicy)、丢弃最旧任务(DiscardOldestPolicy)。

线程池的“拒绝策略”不是故障,而是系统在资源耗尽时的主动选择——它发生在核心线程已满、工作队列已满、最大线程数也已达到的前提下,新任务无法再被接纳时所触发的兜底处理逻辑。
拒绝策略什么时候真正生效?
必须同时满足四个条件才会触发:
- 线程池处于运行状态(未关闭)
- 提交的任务数超过 corePoolSize,先进入阻塞队列
- 阻塞队列已满(queue.size() == queueCapacity)
- 当前活跃线程数已达 maximumPoolSize,无法再创建新线程
此时,任务既不能入队、也不能启新线程执行,就只能交由 RejectedExecutionHandler 处理。简单说:拒绝 = maxPoolSize + queueCapacity 全部打满后的临界响应。
四种内置策略各解决什么问题?
AbortPolicy(默认):抛异常,让调用方立刻感知过载。适合强一致性场景,比如支付扣款,宁可失败也不愿滞留或丢数据。需配合 try-catch 和监控告警使用。
CallerRunsPolicy:不丢任务,也不抛异常,而是把任务“还给”提交它的线程去同步执行。本质是反压机制——调用线程变慢,自然降低上游提交速度。适合日志、异步通知等允许延迟但不可丢失的场景。
DiscardPolicy:静默丢弃,零开销。适合采样类任务,例如每秒上报10次心跳,丢几个不影响整体统计,追求吞吐优先。
DiscardOldestPolicy:丢掉队列里最老的那个任务,腾出一个位置尝试重新提交当前任务。适用于新任务时效性远高于旧任务的场景,比如实时消息推送、行情刷新。
为什么不能只靠调大线程池或队列?
盲目扩大 maximumPoolSize 或 queueCapacity 可能掩盖真实瓶颈:
- 线程过多 → 上下文切换开销剧增,CPU反而利用率下降
- 队列过大 → 任务积压严重,响应延迟不可控,甚至引发 OOM
拒绝策略其实是系统健康度的“仪表盘”。它倒逼你思考:这个任务到底该重试?降级?记录后异步补偿?还是本就不该进来?真正的稳定性来自策略背后的业务判断,而非参数堆砌。
自定义策略的关键考量点
实现 RejectedExecutionHandler 接口时,重点关注三件事:
- 是否保留任务上下文:比如序列化任务参数,写入 DB 或 MQ,供后续重放
-
是否触发告警或指标上报:如 Prometheus 记录
threadpool_rejected_total - 是否影响调用链路:避免在拒绝逻辑中做远程调用或长耗时操作,防止阻塞主线程
一个健壮的自定义策略,往往不是“怎么执行”,而是“怎么记、怎么告、怎么补”。

















