第14章 线程池 RejectedExecutionException 与拒绝策略源码
14.1 触发条件的精确边界:实测验证
很多人对"线程池什么时候会拒绝任务"只有模糊印象,用最小化参数实测精确边界:
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1)); // 核心线程1,最大线程1,队列容量1
for (int i = 0; i < 5; i++) {
pool.execute(task_i);
}
实测结果(JDK 21):
ini
submitted 0 ← 核心线程立即执行(active threads = 1)
submitted 1 ← 进入队列(queued tasks = 1)
task 2 rejected: ... pool size = 1, active threads = 1, queued tasks = 1 ...
task 3 rejected: ...
task 4 rejected: ...
task 0 done
task 1 done
精确验证了 ThreadPoolExecutor 的任务分发逻辑(源码 execute() 方法的三段式判断):
- 当前运行线程数 <
corePoolSize→ 创建新线程直接执行。 - 否则尝试放入队列 → 队列未满则入队等待。
- 队列也满了 → 尝试创建新线程(前提是当前线程数 <
maximumPoolSize)。 - 以上都不满足(本例中
maximumPoolSize也等于 1,无法再创建新线程)→ 触发拒绝策略。
本例中 core=max=1,第 3 步永远不可能成立,所以核心线程执行 1 个 + 队列容纳 1 个之后,第 3、4、5 个任务必然全部被拒绝,异常信息里明确带出了拒绝时刻线程池的完整状态快照(pool size / active threads / queued tasks / completed tasks),这是排查线程池打满问题时最直接的诊断依据。
14.2 四种内置拒绝策略源码级对比
java
// 1. AbortPolicy(默认):直接抛异常,最"暴力"但也最能第一时间暴露问题
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
// 2. CallerRunsPolicy:把任务丢回给"提交任务的线程"自己同步执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); // 注意:是直接调用 run(),不是提交到线程池,因此这里会阻塞调用线程
}
}
}
// 3. DiscardPolicy:静默丢弃,不抛异常也不记录任何日志------生产环境慎用,容易丢任务却毫无感知
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }
}
// 4. DiscardOldestPolicy:丢弃队列中最老(排队最久)的任务,把位置让给新任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 从队列头部丢弃一个
e.execute(r); // 重新提交当前任务
}
}
}
CallerRunsPolicy 的"降速"效果原理 :当线程池打满,提交任务的线程(通常是接收请求的主线程,比如 Web 容器的请求处理线程)会被迫自己同步执行这个任务,这段时间内该线程无法再去接收/提交新的请求,客观上起到了给上游限速的作用------这是一种朴素但有效的背压(backpressure)机制,代价是可能拖慢请求处理线程的响应速度。
生产环境选型建议 :核心业务链路通常不建议用 DiscardPolicy (静默丢任务,出问题很难被发现,往往是通过"业务数据对不上"这种滞后信号才暴露),更推荐 AbortPolicy 配合主动 catch 后走降级逻辑(比如写入延迟队列/落库后续补偿),或者 CallerRunsPolicy 配合合理的超时控制。