threadpoolexecutor 拒绝任务时触发 rejectedexecutionhandler 执行兜底逻辑,而非抛异常;四种内置策略各具特点:abortpolicy 抛异常、callerrunspolicy 由调用线程执行、discardpolicy 静默丢弃、discardoldestpolicy 丢队首再重试;自定义策略需避免阻塞、确保补偿可靠、限流日志;拒绝策略是压力报警器,需结合有界队列、合理线程数与监控协同调优。

ThreadPoolExecutor 拒绝任务时到底发生了什么
当线程池的 workQueue 已满且核心/最大线程数已达上限,新提交的 Runnable 或 Callable 会被拒绝——这不是异常,而是明确设计的行为。此时 RejectedExecutionHandler 被触发,它不抛错、不重试、不排队,只执行你指定的兜底逻辑。
四种内置拒绝策略的区别和适用场景
Java 提供了四个静态工厂方法,对应不同兜底行为,选错会导致静默丢任务或线程阻塞:
-
AbortPolicy(默认):抛RejectedExecutionException,适合能立即感知失败的场景,比如后台批处理;但 Web 请求中直接抛异常可能让接口不可用 -
CallerRunsPolicy:由提交任务的线程(比如 Tomcat 的 worker 线程)自己执行该任务,会降低提交速度,但能缓解压力;注意它可能拖慢调用方,不适合高吞吐 HTTP 接口 -
DiscardPolicy:静默丢弃,无日志无提示;适合允许丢失的指标上报类任务 -
DiscardOldestPolicy:丢掉队列头的任务,再尝试重新提交当前任务;但若队列持续满,可能反复丢老任务,且不保证公平性
自定义拒绝策略必须注意的三个坑
写自己的 RejectedExecutionHandler 很容易踩坑:
- 不能在
rejectedExecution方法里调用executor.submit()或类似阻塞操作,否则可能引发死锁或线程耗尽 - 如果做异步补偿(比如写 Kafka 或落库),必须确保补偿逻辑本身有超时和失败降级,否则拒绝处理成了新瓶颈
- 别在拒绝逻辑里打印完整堆栈(
e.printStackTrace()),高频拒绝时 I/O 会打满磁盘或日志系统;改用带限流的logger.warn("task rejected", e)
线程池配置和拒绝策略不是独立问题
单独调优 RejectedExecutionHandler 没意义,它只是溢出后的结果。真正要动的是上游水位:
立即学习“Java免费学习笔记(深入)”;
- 检查
workQueue类型:LinkedBlockingQueue默认无界,等于把拒绝逻辑“关掉了”,实际压垮的是堆内存;生产环境务必用有界队列(如ArrayBlockingQueue) -
corePoolSize和maximumPoolSize差距太大,配合keepAliveTime过长,会导致线程空转占用资源,挤压真实任务空间 - 监控必须覆盖
getQueue().size()和getActiveCount(),光看拒绝数只能知道“已经晚了”
拒绝策略不是容错开关,是压力报警器。它一响,说明队列、线程数、任务生成速率三者之间早就不平衡了。










