线程池拒绝策略是系统过载时主动调控流量、保障核心逻辑的关键控制点,需分层设计:核心任务用AbortPolicy或带告警补偿的自定义策略,非核心任务才用CallerRunsPolicy或DiscardOldestPolicy,严禁混用线程池;CallerRunsPolicy须规避远程调用、设置超时、监控队列水位;拒绝时必须记录traceId、任务类型等上下文并分级告警,可补偿任务需同步写入延迟队列或DB;自定义策略应统一完成记录、告警、降级三件事,实现可配置、可灰度、可追踪的工程化降级能力。

线程池拒绝策略不是兜底补丁,而是系统过载时主动调节流量、保核心逻辑的关键控制点。真正“优雅”的降级,核心在于:不丢关键任务、不拖垮上游、可观测可干预。
明确哪些是必须保住的“核心变量逻辑”
先识别业务中不可降级的部分,比如:支付扣款、库存预占、风控决策、订单创建主链路。这些逻辑一旦失败,会直接引发资损或用户投诉。而像日志上报、异步通知、埋点统计、非实时推荐等,属于可降级甚至可丢弃的辅助逻辑。拒绝策略的选择,必须围绕这个分层展开:
- 核心逻辑任务提交时,应使用 AbortPolicy 或带告警+补偿的自定义策略,确保异常显式暴露,触发熔断或人工介入
- 非核心逻辑任务提交时,才考虑 CallerRunsPolicy 或 DiscardOldestPolicy,用可控方式释放压力
- 避免把所有任务塞进同一个线程池——混合调度会让拒绝策略失效,也掩盖真实瓶颈
CallerRunsPolicy 不是万能兜底,要用对场景和边界
它让调用线程执行任务,本质是把压力“反压”回上游,从而自然限流。但必须警惕副作用:
- 只适用于调用线程本身具备执行能力且耗时可控的任务,比如轻量计算、本地缓存更新;绝不能用于含远程调用(HTTP/MQ/DB)的任务,否则会卡死 Tomcat 或 Netty 工作线程
- 必须配合超时控制:在任务 Runnable 内部设置明确的执行超时(如
CompletableFuture.orTimeout(500, TimeUnit.MILLISECONDS)),防止调用线程被长阻塞 - 建议搭配队列水位监控:当
executor.getQueue().size()持续 > 80% 容量时,提前触发告警,而不是等 CallerRuns 被大量触发才感知
拒绝发生时,必须携带上下文并分级响应
一次 RejectedExecutionException 不是终点,而是诊断起点。裸抛异常或静默丢弃都会让问题不可追溯:
- 记录日志时必须包含:traceId、任务类型(如 “sms-notify”)、线程池名(如 “notify-tp”)、当前队列大小、活跃线程数、拒绝策略类型
- 按影响等级做分级告警:P0(核心交易类拒绝)立即电话告警;P1(非核心但需补偿)走企业微信+钉钉;P2(日志类丢弃)仅记录指标,不打扰
- 对可补偿任务(如 MQ 发送失败),拒绝时同步写入延迟队列或 DB 补偿表,并由后台定时任务重试,而非依赖“下次再试”
用自定义策略把降级逻辑收口到一处
JDK 内置策略太粗粒度。一个生产就绪的拒绝策略应同时完成三件事:记录、告警、降级。示例如下:
public class GracefulRejectHandler implements RejectedExecutionHandler {
private final String poolName;
private final MeterRegistry meterRegistry;
public GracefulRejectHandler(String poolName, MeterRegistry meterRegistry) {
this.poolName = poolName;
this.meterRegistry = meterRegistry;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1. 打标指标
Counter.builder("threadpool.rejected")
.tag("pool", poolName)
.tag("reason", "queue_full")
.register(meterRegistry)
.increment();
// 2. 记录带上下文的日志
log.warn("[REJECT] pool={} queueSize={} active={} task={}",
poolName,
executor.getQueue().size(),
executor.getActiveCount(),
getTaskName(r));
// 3. 核心任务走补偿,非核心任务静默丢弃
if (r instanceof CriticalTask) {
compensationService.submitLater((CriticalTask) r);
}
}
private String getTaskName(Runnable r) {
return r.getClass().getSimpleName() + "@" + System.identityHashCode(r);
}
}
这样既守住底线,又把降级行为变成可配置、可灰度、可追踪的工程能力。

















