rejectedexecutionexception 不仅在线程池满时抛出,还可能因线程池已 shutdown 或 shutdownnow 而立即触发;其根本原因是 execute() 提交时线程池判定“此刻无法接受新任务”。

RejectedExecutionException 是线程池满了才抛的吗?
不是。它只说明 execute() 提交任务时,线程池判定“此刻无法接受新任务”,但原因不一定是“队列已满 + 线程数已达最大”。比如线程池已 shutdown() 或 shutdownNow(),再调 execute() 也会立刻抛这个异常。
常见错误现象包括:
- 日志里突然出现大量
RejectedExecutionException,但监控显示队列使用率不到 10% - 服务刚启动就报错,而线程池配置明明是默认的
LinkedBlockingQueue(无界队列)
关键判断点:先看线程池状态,再看拒绝策略和队列类型。无界队列(如 LinkedBlockingQueue)+ 默认 AbortPolicy,只有在 shutdown 后才会触发该异常;而有界队列(如 ArrayBlockingQueue)+ CallerRunsPolicy,可能在高并发瞬间压垮调用线程。
怎么选拒绝策略才不丢任务又不雪崩?
拒绝策略不是兜底方案,而是流量整形的最后关卡。选错会放大故障——比如用 AbortPolicy(默认)直接丢任务,上游没重试就等于数据丢失;用 CallerRunsPolicy 可能反向拖慢 HTTP 请求线程,引发超时连锁反应。
立即学习“Java免费学习笔记(深入)”;
根据场景选:
- 对延迟敏感、可容忍少量丢失的任务(如埋点上报):用
DiscardPolicy或DiscardOldestPolicy - 需要保任务、且调用方能承受同步阻塞:用
CallerRunsPolicy,但必须确保调用线程本身有超时和降级 - 要记录拒绝行为并告警:自定义策略,继承
RejectedExecutionHandler,在rejectedExecution()里打日志 + 上报指标,别在这里做重试或异步落盘(可能引发二次拒绝)
ThreadPoolExecutor 构造时哪些参数联动影响拒绝行为?
拒绝是否触发,取决于三个参数的协同:
-
corePoolSize和maximumPoolSize决定线程扩容边界 -
workQueue类型决定缓冲能力:无界队列(LinkedBlockingQueue)几乎不触发拒绝(除非 shutdown),有界队列(ArrayBlockingQueue)容量一到就看策略 -
keepAliveTime影响空闲线程回收速度,间接影响突发流量下能否及时扩容
典型陷阱:
- 把
ArrayBlockingQueue容量设为 100,但maximumPoolSize设成 10 → 队列满后线程池不再扩容,任务直接被拒 - 用
SynchronousQueue(容量为 0)配小的maximumPoolSize→ 没缓冲,全靠线程撑,稍有抖动就拒绝
线上发生 RejectedExecutionException 后第一件事做什么?
立刻查两件事,别急着改代码:
- 查线程池当前状态:
isShutdown()、isTerminated(),确认是不是被意外关闭 - 查队列剩余容量:
queue.remainingCapacity(),结合getActiveCount()和getPoolSize()判断是真饱和还是假饱和(比如线程卡死、任务阻塞未完成)
如果发现是 shutdown 引起的,回溯调用链——常见原因是 Spring Bean 销毁方法、JVM 关闭钩子、或配置了 @PreDestroy 却没控制好执行时机。这种问题改策略没用,得修生命周期管理。
真正难处理的是“偶发性拒绝”:流量毛刺 + 队列锁竞争 + GC 暂停共同导致的瞬时堆积。这时候加机器、调参数效果有限,得从上游限流(如 Sentinel)、任务拆分(大任务切片)、或异步化补偿(拒绝后写入 Kafka 重试)入手。单纯调大队列或线程数,往往只是把问题延后爆发。










