线程池阻塞队列未满却抛出拒绝异常的原因排查
我的线程池配置如下:
- corePoolSize:1024
- maximumPoolSize:1024
- workQueue:容量为1024的LinkedBlockingQueue
- RejectedExecutionHandler:直接抛出RejectedExecutionException
线程池运行期间抛出拒绝异常,但监控面板及异常日志显示核心线程活跃数、阻塞队列长度均不足100。异常触发于ThreadPoolExecutor的execute方法中调用reject(command)的逻辑,根据源码推测是workQueue.offer(command)返回false,但此时队列容量为1024、实际长度不足100,为何offer()会失败?
异常日志:
Thread pool is EXHAUSTED! Thread Name: xxx, Pool Size: 1024 (active: 93, core: 1024, max: 1024, largest: 1024), Task: 10414879419 (completed: 10414878332), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false, queue:[{}])
ThreadPoolExecutor的execute核心源码:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
结合场景和ThreadPoolExecutor源码逻辑,队列未满但offer()返回false、最终触发拒绝异常,主要有以下几种可能:
1. 高并发下队列瞬时被填满(最可能原因)
从日志的任务总量(已提交超100亿,完成超100亿)能看出任务提交速度极快。当核心线程已全部启动(1024个),所有新任务都会进入队列分支。短时间内大量任务同时入队,会瞬间把队列填满到1024的上限,导致部分offer()调用失败。
而监控面板和异常日志显示的队列长度是异常发生后的状态——此时核心线程已经处理了部分任务,队列长度回落至不足100,就出现了“队列未满却触发拒绝”的矛盾现象。
可以通过实时监控队列瞬时长度(比如用JProfiler等工具,或在offer()前后打印队列长度)验证这个猜想,异常发生时队列必然达到过容量上限。
2. 线程池状态的并发变更(可排除)
execute方法中isRunning(c)是调用offer()的前提,但如果在isRunning(c)检查之后、offer()调用之前,其他线程触发了线程池的shutdown()/shutdownNow(),会导致后续逻辑触发拒绝。不过你的日志明确显示isShutdown:false,线程池仍处于RUNNING状态,这个可能性可以排除。
3. LinkedBlockingQueue的异常状态(低概率)
LinkedBlockingQueue基于ReentrantLock保证线程安全,正常情况下offer()只会在队列满时返回false。但如果存在非常规操作(比如通过反射修改队列容量),会导致实际容量小于配置的1024,此时监控显示的“队列未满”其实已经达到了修改后的容量上限,offer()返回false。这种情况属于代码违规操作,概率极低。
4. Worker线程统计的并发延迟(低概率)
线程池配置corePoolSize=maximumPoolSize=1024,execute第一步会检查工作线程数是否小于核心数。如果有核心线程因未捕获异常意外死亡,理论上ThreadPoolExecutor会自动更新workerCount,但极端并发场景下可能存在统计延迟,导致workerCountOf(c)显示为1024(无法新建线程),但实际可用线程不足,任务只能进入队列。不过你的日志显示Pool Size:1024,说明workerCount统计准确,这个可能性也很低。
内容的提问来源于stack exchange,提问作者Ohio-HYF

