Java线程池默认拒绝策略为AbortPolicy,抛出RejectedExecutionException;另有CallerRunsPolicy(调用线程执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队列头任务后重试)。

Java线程池的四种内置拒绝策略
Java线程池(ThreadPoolExecutor)在任务提交时,若线程数已达 maximumPoolSize 且工作队列已满,就会触发拒绝策略。JDK 提供了四个预定义实现类,全部位于 java.util.concurrent.ThreadPoolExecutor 内部:
-
AbortPolicy:默认策略,直接抛出RejectedExecutionException异常 -
CallerRunsPolicy:由调用线程(即 submit() 所在线程)同步执行该任务 -
DiscardPolicy:静默丢弃任务,不抛异常也不通知 -
DiscardOldestPolicy:丢弃队列头部任务(最老的),再尝试重新提交当前任务
如何自定义拒绝策略
只要实现 RejectedExecutionHandler 接口即可,核心是重写 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。常见自定义需求包括记录日志、发送告警、降级写入磁盘或 Kafka 等。
注意点:
- 该方法运行在提交线程中,避免阻塞或耗时操作(如远程调用),否则会拖慢上游
- 不要在 handler 中再次调用
executor.execute(r),可能引发无限递归或死锁 - 若需异步处理被拒任务,建议用独立线程池或消息队列缓冲,而非复用原池
示例(简单日志+计数):
立即学习“Java免费学习笔记(深入)”;
public class LoggingRejectHandler implements RejectedExecutionHandler {
private final AtomicInteger rejectedCount = new AtomicInteger(0);
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("Task rejected: " + r + ", pool size=" + executor.getPoolSize());
rejectedCount.incrementAndGet();
}
}
拒绝策略何时真正生效
很多人误以为“队列满了就触发”,其实触发条件更严格:必须同时满足以下三点:
- 当前线程数
→ 优先创建新线程,不进队列 - 当前线程数
> corePoolSize且→ 尝试进队列;若队列未满则入队,满则创建新线程直到达maximumPoolSize - 当前线程数
== maximumPoolSize且队列已满 → 才真正触发拒绝策略
也就是说,LinkedBlockingQueue 这类无界队列(默认容量 Integer.MAX_VALUE)几乎永远不会触发拒绝策略——除非内存耗尽 OOM,这不是策略能控制的。
选择策略的关键权衡点
选哪个不是看“功能炫酷”,而是看业务对可靠性、延迟、可观测性的容忍度:
- 金融/支付类:倾向
AbortPolicy+ 全链路监控,宁可快速失败也不掩盖问题 - 后台批处理:可用
CallerRunsPolicy,让上游线程背压,天然限流 - 日志收集类:适合
DiscardPolicy,丢几条日志影响小,但不能卡住主流程 -
DiscardOldestPolicy在缓存刷新、心跳等场景有用,但要注意队列是 FIFO,丢的是最旧任务,未必是最不重要任务
真正容易被忽略的是:拒绝策略只管“怎么处理被拒任务”,不管“为什么被拒”。排查时得回溯队列类型、corePoolSize/maximumPoolSize 设置、任务平均耗时与提交频率——策略只是最后一道闸门,不是调优终点。










