线程池拒绝异常由调用execute/submit的线程抛出,须在提交任务处try-catch捕获RejectedExecutionException,不可依赖线程池内部处理;自定义策略可抛出TaskRejectedException,但仍需调用方捕获。

线程池拒绝策略中的异常捕获,关键在于区分“谁抛出”和“谁接收”。默认的 AbortPolicy 会在任务被拒绝时直接抛出 RejectedExecutionException,这个异常不是在线程池内部被吞掉的,而是**向调用 execute() 或 submit() 的那个线程抛出**。所以捕获动作必须写在提交任务的代码位置,而不是在线程池配置里。
明确异常抛出时机与位置
只有当线程池真正无法接纳新任务时(例如:最大线程数已达、队列已满、或线程池已关闭),execute() 方法才会触发拒绝策略并抛出异常。注意:submit() 内部也调用了 execute(),因此同样会触发。
- 异常不会在线程池工作线程中出现,而是在你调用
executor.execute(runnable)的那一行代码处抛出 - 该异常是运行时异常(
RuntimeException子类),编译器不强制要求catch,但生产环境必须处理 - 若未捕获,异常会向上冒泡,可能造成 Web 请求返回 500、定时任务中断、或主线程退出
在业务代码中显式捕获 RejectedExecutionException
每次提交任务前,都应包裹 try-catch 块,针对 RejectedExecutionException 做降级响应。
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
- 记录带上下文的日志,如任务类型、当前活跃线程数、队列长度
- 对核心链路(如支付、下单)可落库或发消息到死信队列,后续异步重试
- 对非关键任务(如日志上报)可降级为同步执行(仅限轻量逻辑),或直接返回默认值
- 避免在
catch块中再次调用executor.execute(),否则可能引发死循环或雪崩
使用自定义拒绝策略抛出自定义异常
如果需要更清晰的语义或统一异常体系,可实现 RejectedExecutionHandler 并主动抛出你的异常类(如 TaskRejectedException)。
立即学习“Java免费学习笔记(深入)”;
- 自定义异常需继承
RuntimeException或Exception,构造函数支持携带消息和原因 - 拒绝处理器中不要调用
super.rejectedExecution(),直接throw new TaskRejectedException(...) - 该异常仍由提交线程接收,因此调用方仍需
try-catch,只是异常类型变了 - 可在异常信息中注入线程池状态(如
activeCount、queue.size()),便于问题定位
避免常见误区
有些做法看似合理,实则隐藏风险:
- 不捕获、不处理:依赖默认行为,导致服务偶发不可用,监控无告警
-
用
CallerRunsPolicy替代异常捕获:把任务压回 Tomcat 线程执行,可能阻塞请求处理,放大延迟 -
在拒绝策略里重试提交:如
executor.execute(r),极易造成线程池持续饱和甚至 OOM -
仅靠加大队列缓解压力:有界队列过大易堆内存溢出;无界队列(如
LinkedBlockingQueue)更危险,会无限堆积

















