线程池的四种拒绝策略各适用什么场景?
结论先行:当任务队列已满且线程数达到 maximumPoolSize 时,新任务会交给拒绝策略处理。ThreadPoolExecutor 内置四种策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者线程执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队列中最老任务),也可以实现 RejectedExecutionHandler 自定义降级逻辑。
四种策略对比
| 策略 | 行为 | 风险 / 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 抛 RejectedExecutionException | 快速暴露过载,适合可接受失败并重试的业务 |
| CallerRunsPolicy | 由提交任务的线程直接执行 | 天然背压限流、不丢任务,适合写库等关键任务 |
| DiscardPolicy | 悄悄丢弃新任务 | 适合可容忍丢失的日志、统计类任务 |
| DiscardOldestPolicy | 丢弃队头最老任务再重试提交 | 追求“留新弃旧”,如实时价格/行情刷新 |
使用注意
- 任何拒绝都说明“池 + 队列”容量配小了,应先按 QPS × 平均响应时间估算后再定参。
- 生产环境常用自定义策略:记录告警日志并降级(如转存 MQ、走兜底数据源),而不是裸抛异常。
import java.util.concurrent.*;
public class RejectDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy()); // 慢下来而不是丢
for (int i = 0; i < 5; i++) {
final int no = i;
pool.execute(() -> System.out.println("执行 " + no));
}
pool.shutdown();
}
}
常见追问 / 记忆点
- 记忆点:Abort 抛异常、Caller 自己扛、Discard 静默丢、Oldest 丢最老。
- 追问:默认策略是 AbortPolicy,最安全但也最“粗暴”,线上一般要自定义成降级+告警。
- 追问:CallerRunsPolicy 把压力回传给提交线程,能形成“提交变慢 → 源头限流”的闭环。
- 追问:真正的容量 = 队列长度 + maximumPoolSize;按 QPS×RT 估算后建议预留 20%~30% 余量。