threadpoolexecutor底层工作原理是“任务提交—线程分配—队列缓冲—扩容回收—拒绝兜底”的闭环机制,触发拒绝策略需同时满足:核心线程全忙、队列已满、线程数达maximumpoolsize且无空闲线程。

ThreadPoolExecutor 的底层工作原理围绕“任务提交—线程分配—队列缓冲—扩容回收—拒绝兜底”这一闭环展开,不是简单地排队或起线程,而是一套有状态、有优先级、有双重校验的协同机制。任务拒绝策略只是这个闭环在资源耗尽时的最终出口,它的触发本身已说明前面所有环节都已饱和。
任务是怎么被处理的:execute() 的五步流转逻辑
每次调用 execute(Runnable),线程池内部按固定顺序尝试处理任务:
- 先检查当前线程数是否小于 corePoolSize;是,则直接新建核心线程执行,不入队
- 否则尝试把任务 offer 到 workQueue;成功则等待已有线程消费,不创建新线程
- offer 成功后,会二次检查线程池状态(防止刚入队就 shutdown),若发现线程数为 0,主动 addWorker(null, false) 唤醒一个空闲线程处理队列
- 如果队列已满,且当前线程数仍小于 maximumPoolSize,则新建非核心线程执行该任务
- 以上全部失败(队列满 + 线程已达 maximumPoolSize + 所有线程忙碌),才走到 reject(command)
拒绝策略触发的三个硬性前提
拒绝不是随机发生的,必须同时满足以下三点:
- 所有 核心线程都在运行中(workerCount ≥ corePoolSize)
- 任务队列 已满(workQueue.offer() 返回 false)
- 当前线程总数已达 maximumPoolSize,且没有空闲线程可调度
缺一不可。例如即使队列没满,但线程数已达上限且全忙,任务仍会入队;反过来,队列满了但还有扩容空间,就会起新线程——只有三者全占满,才会进拒绝流程。
四种内置拒绝策略的行为差异
JDK 提供的策略本质是「谁来承担失败成本」的问题:
- AbortPolicy(默认):抛出 RejectedExecutionException,由调用方决定重试、降级或告警
- CallerRunsPolicy:让提交任务的线程(比如 Tomcat 的 worker 线程)自己同步执行该任务,起到自然限流作用,但可能拖慢上游
- DiscardPolicy:静默丢弃,不报错也不通知,适合允许丢失的场景(如日志采集)
- DiscardOldestPolicy:丢掉队列头任务(最老的),再尝试重新 submit 当前任务;可能引发重复丢弃,需谨慎
线程池状态如何影响拒绝时机
ThreadPoolExecutor 内部用一个 AtomicInteger ctl 同时存状态(高 3 位)和线程数(低 29 位)。只有处于 RUNNING 状态才接受新任务;一旦调用 shutdown(),后续 submit 就会立即触发 reject,哪怕队列还有空位、线程也未满——因为此时线程池已进入“只消费不接单”模式。这也是为什么生产环境做优雅停机时,要等队列清空再 awaitTermination。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











