【多线程】---线程池 ThreadPoolExecutor 原理与调优

基于:JDK 17(java.util.concurrent.ThreadPoolExecutor,JDK 1.5 引入,8~17 核心逻辑一致,虚拟线程 JDK 21 另文)

本文从源码主线(execute → addWorker → runWorker → getTask → 关闭流程)讲透线程池"状态机 + Worker + 队列"三件事,再落到面试与生产都绕不开的参数调优:核心线程数怎么算、队列多大、何时触发拒绝,附 2 个可运行 Demo 复现"core → queue → max → reject"的提交顺序与线程回收、超时回收机制,最后给出生产模板与高频问答。


目录

  1. 核心理论
    • [1.1 一句话定位](#1.1 一句话定位)
    • [1.2 它解决什么问题](#1.2 它解决什么问题)
    • [1.3 线程池全景定位表](#1.3 线程池全景定位表)
    • [1.4 执行顺序总纲](#1.4 执行顺序总纲)
  2. 设计思想与核心机制
    • [2.1 状态打包 ctl 与生命周期状态机](#2.1 状态打包 ctl 与生命周期状态机)
    • [2.2 任务提交链路 execute](#2.2 任务提交链路 execute)
    • [2.3 线程的诞生 addWorker](#2.3 线程的诞生 addWorker)
    • [2.4 线程的一生 runWorker](#2.4 线程的一生 runWorker)
    • [2.5 线程的回收 getTask](#2.5 线程的回收 getTask)
    • [2.6 Worker 与 AQS 独占锁](#2.6 Worker 与 AQS 独占锁)
    • [2.7 阻塞队列选型](#2.7 阻塞队列选型)
    • [2.8 四种拒绝策略](#2.8 四种拒绝策略)
    • [2.9 关闭流程 shutdown 家族](#2.9 关闭流程 shutdown 家族)
    • [2.10 扩展钩子与 ScheduledThreadPoolExecutor](#2.10 扩展钩子与 ScheduledThreadPoolExecutor)
  3. 主线流程剖析
    • [3.1 提交与执行完整链路](#3.1 提交与执行完整链路)
    • [3.2 线程回收决策流程](#3.2 线程回收决策流程)
    • [3.3 关闭状态机流转](#3.3 关闭状态机流转)
    • [3.4 一次带回收的完整时序](#3.4 一次带回收的完整时序)
  4. 类关系图
    • [4.1 继承体系全景](#4.1 继承体系全景)
    • [4.2 内部对象关系](#4.2 内部对象关系)
  5. 参数调优实战
    • [5.1 七大参数角色与调优影响](#5.1 七大参数角色与调优影响)
    • [5.2 核心线程数估算公式](#5.2 核心线程数估算公式)
    • [5.3 队列容量与峰值兜底](#5.3 队列容量与峰值兜底)
    • [5.4 拒绝策略与业务选型](#5.4 拒绝策略与业务选型)
    • [5.5 线程工厂与异常兜底](#5.5 线程工厂与异常兜底)
    • [5.6 动态调参思路](#5.6 动态调参思路)
    • [5.7 可运行 Demo 1 提交顺序实证](#5.7 可运行 Demo 1 提交顺序实证)
    • [5.8 可运行 Demo 2 超时回收与动态调整](#5.8 可运行 Demo 2 超时回收与动态调整)
    • [5.9 生产环境模板代码](#5.9 生产环境模板代码)
  6. [扩展点与常见问题 FAQ](#扩展点与常见问题 FAQ)
    • [6.1 高频面试问答](#6.1 高频面试问答)
    • [6.2 源码细节点辨析](#6.2 源码细节点辨析)
    • [6.3 总结与学习路径](#6.3 总结与学习路径)

1. 核心理论 ★★★

1.1 一句话定位

关键认知 :ThreadPoolExecutor = 一个打包了"运行状态 + 线程数"的 AtomicInteger(ctl)+ 一份线程集合(workers)+ 一条任务队列(workQueue)

它的全部行为可以浓缩成一句话:核心线程干活 → 干不完进队列排队 → 队列满了再扩线程到 max → 还扛不住就按拒绝策略处理。

java 复制代码
public class ThreadPoolExecutor extends AbstractExecutorService {

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // ctl 高 3 位 = runState(线程池生命周期),低 29 位 = workerCount(当前线程数)
    private final BlockingQueue<Runnable> workQueue;   // 任务缓冲队列
    private final HashSet<Worker> workers = new HashSet<>();  // 线程集合(需 mainLock 保护)
    private int corePoolSize;        // 核心线程数
    private int maximumPoolSize;     // 最大线程数
    private long keepAliveTime;      // 空闲线程存活时间
    private ThreadFactory threadFactory;         // 线程工厂
    private RejectedExecutionHandler handler;    // 拒绝策略
}

1.2 它解决什么问题

裸用线程的问题 线程池的解法
每任务建线程:创建/销毁开销大(系统调用、栈 1MB+、GC 压力) 线程复用:跑完任务不销毁,回池继续接活
无界并发:1 万请求开 1 万线程,CPU 空转、内存耗尽 池化限流:core/max 双上限 + 队列缓冲
突发流量无缓冲,直接压垮系统 队列削峰:瞬时任务先排队,平滑消费
无生命周期管理:停机时线程泄漏、任务丢 状态机 + 关闭协议:shutdown/awaitTermination 优雅停机
无法观测 内置指标:getPoolSize/getActiveCount/getQueue().size() 等

1.3 线程池全景定位表

工厂方法 线程数 队列 特点与风险
Executors.newFixedThreadPool(n) core=max=n LinkedBlockingQueue 无界 线程数恒定;⚠️ 队列无限积压 → 内存 OOM、任务无限延迟,max 形同虚设
Executors.newCachedThreadPool() core=0,max=Integer.MAX SynchronousQueue 空闲 60s 回收;⚠️ 突发流量线程暴涨(上限 21 亿)→ 耗尽资源
Executors.newSingleThreadExecutor() 1 无界 保证顺序;⚠️ 无界队列同上;异常退出自动补线程
Executors.newScheduledThreadPool(n) core=n DelayedWorkQueue 定时/周期任务;本质是 ScheduledThreadPoolExecutor
Executors.newWorkStealingPool() 并行度参数 双端队列 ⚠️ 返回的是 ForkJoinPool,不是 ThreadPoolExecutor(工作窃取模型)

⚠️ 阿里巴巴 Java 开发手册(强制):线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式显式配置------规避无界队列 OOM 与线程数暴涨两类事故。

1.4 执行顺序总纲

线程池的扩容逻辑是严格分段的 ,一段走完才进入下一段:

① workerCount < corePoolSize → 新建核心线程立即执行 → ② 否则任务入队 → ③ 队列满 → 新建非核心线程到 maximumPoolSize → ④ 满员且队列满 → 拒绝策略
#mermaid-svg-cVhBpfHbJzV0YZFJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cVhBpfHbJzV0YZFJ .error-icon{fill:#552222;}#mermaid-svg-cVhBpfHbJzV0YZFJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cVhBpfHbJzV0YZFJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .marker.cross{stroke:#333333;}#mermaid-svg-cVhBpfHbJzV0YZFJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cVhBpfHbJzV0YZFJ p{margin:0;}#mermaid-svg-cVhBpfHbJzV0YZFJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster-label text{fill:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster-label span{color:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster-label span p{background-color:transparent;}#mermaid-svg-cVhBpfHbJzV0YZFJ .label text,#mermaid-svg-cVhBpfHbJzV0YZFJ span{fill:#333;color:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .node rect,#mermaid-svg-cVhBpfHbJzV0YZFJ .node circle,#mermaid-svg-cVhBpfHbJzV0YZFJ .node ellipse,#mermaid-svg-cVhBpfHbJzV0YZFJ .node polygon,#mermaid-svg-cVhBpfHbJzV0YZFJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .rough-node .label text,#mermaid-svg-cVhBpfHbJzV0YZFJ .node .label text,#mermaid-svg-cVhBpfHbJzV0YZFJ .image-shape .label,#mermaid-svg-cVhBpfHbJzV0YZFJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-cVhBpfHbJzV0YZFJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .rough-node .label,#mermaid-svg-cVhBpfHbJzV0YZFJ .node .label,#mermaid-svg-cVhBpfHbJzV0YZFJ .image-shape .label,#mermaid-svg-cVhBpfHbJzV0YZFJ .icon-shape .label{text-align:center;}#mermaid-svg-cVhBpfHbJzV0YZFJ .node.clickable{cursor:pointer;}#mermaid-svg-cVhBpfHbJzV0YZFJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .arrowheadPath{fill:#333333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cVhBpfHbJzV0YZFJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cVhBpfHbJzV0YZFJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cVhBpfHbJzV0YZFJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster text{fill:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ .cluster span{color:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cVhBpfHbJzV0YZFJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cVhBpfHbJzV0YZFJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-cVhBpfHbJzV0YZFJ .icon-shape,#mermaid-svg-cVhBpfHbJzV0YZFJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cVhBpfHbJzV0YZFJ .icon-shape p,#mermaid-svg-cVhBpfHbJzV0YZFJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cVhBpfHbJzV0YZFJ .icon-shape .label rect,#mermaid-svg-cVhBpfHbJzV0YZFJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cVhBpfHbJzV0YZFJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cVhBpfHbJzV0YZFJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cVhBpfHbJzV0YZFJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





新任务提交
workerCount < core ?
新建核心线程执行
队列不满 ?
任务进队列排队
workerCount < max ?
新建非核心线程执行
触发拒绝策略

两个反直觉但必须记住的点:

  1. 只要 workerCount 没到 core,新任务就新开线程------即使此时有核心线程空闲(空闲线程阻塞在队列 take 上,无代价;线程数会先补齐到 core);
  2. 队列没满之前绝不会扩到 max------max 线程是"队列也扛不住"的最后手段,不是"线程不够就扩"。

2. 设计思想与核心机制 ★★★

2.1 状态打包 ctl 与生命周期状态机 ★

线程池需要原子地维护两个量:运行状态 runState线程数 workerCount 。如果拆成两个 volatile 字段,修改其一必须加锁才能保证整体一致;AQS 团队的做法是打包进一个 int,一次 CAS 同时改两个量

java 复制代码
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;   // 29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;  // 低 29 位全 1 = 536870911

private static int runStateOf(int c)     { return c & ~CAPACITY; }  // 取高 3 位
private static int workerCountOf(int c)  { return c & CAPACITY; }   // 取低 29 位
private static int ctlOf(int rs, int wc) { return rs | wc; }

// 高 3 位取值(数值上 RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED,可直接比较)
private static final int RUNNING    = -1 << COUNT_BITS;  // 接受新任务 + 处理队列任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;  // 拒绝新任务,队列任务继续
private static final int STOP       =  1 << COUNT_BITS;  // 拒绝新任务 + 丢弃队列 + 中断所有 worker
private static final int TIDYING    =  2 << COUNT_BITS;  // 线程全部终止,即将执行钩子
private static final int TERMINATED =  3 << COUNT_BITS;  // 钩子执行完,终态

关键认知 :因为数值单调递增,源码里 runStateAtLeast(c, STOP)runStateLessThan(c, STOP) 都用比较 而非等值判断------RUNNING 是负数(-1<<29),所以"RUNNING"要特判 runStateOf(c) == RUNNING< SHUTDOWN。把状态设计成可比较序,是这套状态机最优雅的地方。

状态流转:
#mermaid-svg-Mdk4Hfz5dZTfh7BO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .error-icon{fill:#552222;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .marker.cross{stroke:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO p{margin:0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-Mdk4Hfz5dZTfh7BO g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Mdk4Hfz5dZTfh7BO g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Mdk4Hfz5dZTfh7BO .edgeLabel .label text{fill:#333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .label div .edgeLabel{color:#333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO #statediagram-barbEnd{fill:#333333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .cluster-label,#mermaid-svg-Mdk4Hfz5dZTfh7BO .nodeLabel{color:#131300;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .note-edge{stroke-dasharray:5;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-note text{fill:black;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram-note .nodeLabel{color:black;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagram .edgeLabel{color:red;}#mermaid-svg-Mdk4Hfz5dZTfh7BO #dependencyStart,#mermaid-svg-Mdk4Hfz5dZTfh7BO #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-Mdk4Hfz5dZTfh7BO .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Mdk4Hfz5dZTfh7BO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造完成即 RUNNING
shutdown()(中断空闲线程,队列任务跑完)
shutdownNow()(清空队列,中断全部线程)
shutdownNow()(同左)
队列已空 且 workerCount == 0
workerCount == 0
terminated() 钩子执行完毕
RUNNING
SHUTDOWN
STOP
TIDYING
TERMINATED

为什么 shutdown 只中断"空闲"线程? 让正在跑任务的线程自然执行完,队列剩余任务也由活着/空闲的 worker 继续消费直到清空,是"优雅"二字的全部含义。状态机推进由 tryTerminate() 负责(worker 退出、shutdown 时都会尝试,条件满足就把 SHUTDOWN/STOP 推到 TIDYING 并执行 terminated 钩子)。

2.2 任务提交链路 execute

java 复制代码
public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();
    // 第 1 段:核心线程没满 → 直接新开核心线程跑
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) return;
        c = ctl.get();                        // 加失败(池已关闭)→ 重新读状态往下走
    }
    // 第 2 段:池在 RUNNING 且队列能塞下 → 入队
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();              // ★ 二次检查
        if (!isRunning(recheck) && remove(command))   // 入队后池被 shutdown → 移除并拒绝
            reject(command);
        else if (workerCountOf(recheck) == 0)         // 池活着但没有线程可消费(core=0 场景)
            addWorker(null, false);
    }
    // 第 3 段:队列也满了 → 尝试扩非核心线程,扩不动(达 max 或已关闭)→ 拒绝
    else if (!addWorker(command, false))
        reject(command);
}

为什么有 recheck 二次检查? 竞态窗口:任务 offer 入队成功后、判断完成前,线程池恰好被 shutdown------若不复查,这个任务会"安静地烂在队列里"(SHUTDOWN 下 worker 只会在队列清空后退出,任务本身还是会跑......严格说 SHUTDOWN 会跑完队列任务,真正的风险是入队后立刻被 shutdownNow 清空丢弃,且调用方以为已提交成功)。复查保证提交语义:要么任务已入队且池仍可消费,要么立刻移除交给拒绝策略,绝不悬空

2.3 线程的诞生 addWorker

java 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 只在两种状态放行新线程:RUNNING,或 SHUTDOWN 且 firstTask == null(补空转线程去消费队列)
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null)) return false;
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
            if (compareAndIncrementWorkerCount(c)) break retry;   // CAS 抢占"名额"
            c = ctl.get();
            if (runStateOf(c) != rs) continue retry;              // 期间状态变了 → 外层重来
        }
    }
    // ------ 名额到手,下面创建线程(可能失败需回滚)------
    boolean workerStarted = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);            // 内部通过 threadFactory 创建 Thread(还没 start)
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();                  // workers HashSet 必须加锁保护
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) throw new IllegalThreadStateException();
                    workers.add(w);           // 登记进线程集合
                    int s = workers.size();
                    if (s > largestPoolSize) largestPoolSize = s;
                    t.start();                // ★ 启动线程 → 触发 Worker.run() → runWorker()
                    workerStarted = true;
                }
            } finally { mainLock.unlock(); }
        }
    } finally {
        if (!workerStarted) {
            addWorkerFailed(w);               // 回滚:workerCount-1、移除 Worker、尝试终止
        }
    }
    return workerStarted;
}

三个细节:名额与创建分离 (先 CAS 抢线程名额,后建线程,失败回滚------不会超发线程);状态双重校验 (CAS 前后都要看 runState,防止"刚关闭的池又冒出线程");mainLock 只保护 workers 集合,不参与任务执行路径(任务执行完全无锁)。

2.4 线程的一生 runWorker

Worker 启动后进入 runWorker,这是每个线程从生到死的执行循环:

java 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;      // 第一个任务直接开工
    w.firstTask = null;
    w.unlock();                       // state -1 → 0,放行"中断许可"(见 2.6)
    boolean completedAbruptly = true; // 是否"异常退出"
    try {
        while (task != null || (task = getTask()) != null) {   // 没活干就去 getTask 阻塞等
            w.lock();                                          // 执行期间上"忙碌锁"
            // 池进入 STOP 才补中断;否则吞掉中断位让任务不受打扰(Thread.interrupted 会清除标志)
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                    && !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);          // 钩子① 任务执行前
                try {
                    task.run();                   // ★ 真正执行任务(注意是 run 不是 start)
                    afterExecute(task, null);     // 钩子② 任务执行后
                } catch (Throwable ex) {
                    afterExecute(task, ex);       // 异常也通知钩子
                    throw ex;                     // 重新抛出 → 跳出循环 → 线程"暴毙"退出
                }
            } finally {
                task = null;
                w.completedTasks++;               // 该 worker 完成数 +1
                w.unlock();                       // 解锁,恢复"可中断"状态
            }
        }
        completedAbruptly = false;                // 正常退出:getTask 返回 null
    } finally {
        processWorkerExit(w, completedAbruptly);  // 无论生死都要走善后(见 3.2)
    }
}

关键认知 :任务里抛异常(RuntimeException)→ afterExecute 仍会被调用(带异常参数)→ 异常沿栈抛出 → 该线程直接退出 ,不会继续循环!随后 processWorkerExit 发现是异常退出(completedAbruptly=true)会自动补一个新线程------所以 execute 提交的任务异常不会让池"少人",但异常信息只进线程的 stderr(默认不记录日志),这是"execute 丢异常"问题的根源。

2.5 线程的回收 getTask

Worker 没有任务时不会退出,而是阻塞在 getTask;只有当 getTask 返回 null 时线程才走向消亡

java 复制代码
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 条件①:池已关闭且没有活可干(STOP 直接退;SHUTDOWN 需队列空)
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();      // 让出名额
            return null;                 // → runWorker 退出 → 线程结束
        }
        int wc = workerCountOf(c);
        // 是否"计时回收":非核心线程总是计时;核心线程只有 allowCoreThreadTimeOut(true) 才计时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 条件②:超时没等到任务(timedOut)或线程数超上限 → 让出名额退出
        // (wc > 1 || workQueue.isEmpty() 保证:队列还有活时永远至少留 1 个线程)
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c)) return null;
            continue;
        }
        try {
            // 核心区分点:带超时的 poll vs 无限期 take
            Runnable r = timed
                ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)   // 等 keepAliveTime 就超时
                : workQueue.take();                                     // 无限等
            if (r != null) return r;
            timedOut = true;             // poll 超时 → 下一轮判定回收
        } catch (InterruptedException retry) {
            timedOut = false;            // 被 interrupt(如 shutdown)→ 清超时标记重试判定
        }
    }
}

线程退出只有三条路 :① 池进入 STOP / SHUTDOWN 且队列空;② 超时回收(非核心 or 允许超时的核心);③ 线程数被 setMaximumPoolSize 调低到当前值以下。退出时用 CAS 递减 workerCount,队列里还有任务时保底留 1 个线程wc > 1 || workQueue.isEmpty() 的奥义)。

2.6 Worker 与 AQS 独占锁 ★

Worker 是线程池里"一个线程"的化身,它继承了 \[AQS原理知识点] 的独占锁,把锁当作忙碌标志

java 复制代码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;          // 真正干活的线程(由 threadFactory 创建)
    Runnable firstTask;           // 首个任务(null = 纯"备胎线程",从队列取活)
    volatile long completedTasks; // 本 worker 累计完成任务数

    Worker(Runnable firstTask) {
        setState(-1);             // ★ 初始 state = -1:禁止被中断(线程还没 start)
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    public void run() { runWorker(this); }   // 线程启动入口

    // ------ 用 AQS 实现"不可重入独占锁":state 0→1 成功 = 抢到"忙碌锁" ------
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; }
        return false;
    }
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null); setState(0); return true;
    }
    public void lock()     { acquire(1); }   // runWorker 每轮任务前调用
    public void unlock()   { release(1); }
    public boolean tryLock() { return tryAcquire(1); }   // 非阻塞试探

    void interruptIfStarted() {                 // shutdownNow 用:只中断已启动的线程
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted())
            t.interrupt();
    }
}

为什么 Worker 要继承 AQS? 官方注释给的理由:shutdown 要"只中断空闲 worker",需要一个不可重入 的互斥量当忙碌标志------tryLock() 成功 = 该线程此刻没在执行任务(阻塞在 getTask)→ 可以安全 interrupt;失败 = 正在跑任务(runWorker 持锁中)→ 跳过。用 synchronized 或可重入锁做不了"非阻塞试探拿锁",AQS 的 tryAcquire 天然支持。而 setState(-1) 的玄机是:线程刚 new 出来还没 start 时,state=-1 会让 interruptIfStartedgetState() >= 0 判断)拒绝中断它,防止"线程还没跑起来就被 shutdownNow 中断成死胎"。

2.7 阻塞队列选型

队列是 core 与 max 之间的"蓄水池",选型直接决定线程池行为:

队列 结构 有界性 行为特征 搭配建议
LinkedBlockingQueue 链表 默认无界(可传容量) 吞吐高,无界时 max 失效 Fixed/Single 默认队列 ⚠️ 慎用无界
ArrayBlockingQueue 数组 必须设容量 公平模式可配(构造器 fair) 需要严格有界 + 公平排队
SynchronousQueue 无缓冲 视作 0 offer 必须刚好有线程 take 才成功,否则任务"溢出"去扩线程 CachedThreadPool 默认:任务即来即走,绝不积压
DelayQueue 无界 按延迟出队 ScheduledThreadPoolExecutor 内部(DelayedWorkQueue)
PriorityBlockingQueue 无界 按优先级出队 少数"重要任务先跑"场景

2.8 四种拒绝策略

当 execute 三段全走完(已达 max 且队列满),或池已关闭时触发:

策略 行为 适用
AbortPolicy(默认) 直接抛 RejectedExecutionException 明确失败信号,调用方可感知
CallerRunsPolicy 调用者线程自己执行该任务 天然背压:调用方被拖慢 → 上游减速(削峰第一选择)
DiscardPolicy 静默丢弃,无任何提示 ⚠️ 不推荐裸用
DiscardOldestPolicy 丢弃队列队头最老任务,再尝试提交 允许丢旧任务保新的场景(如实时性任务)

⚠️ 所有策略都可能被"池已关闭"触发,DiscardOldestPolicy 与 CallerRunsPolicy 内部都先判断 !executor.isShutdown();生产上无论选哪种,都建议包装一层:记录拒绝现场(任务、队列水位、线程数)告警

2.9 关闭流程 shutdown 家族

方法 状态目标 行为 队列任务 正在执行的任务
shutdown() SHUTDOWN 拒绝新提交;中断空闲 worker 继续执行完 不打断,执行完
shutdownNow() STOP 清空队列返回;中断所有已启动 worker 以 List 返回,不执行 收到中断(配合响应中断的任务)
awaitTermination(timeout, unit) 阻塞等待 配合 shutdown 使用,超时返回 false ------ ------
java 复制代码
// 优雅停机标准三段式
pool.shutdown();                                   // ① 不再接新活
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) { // ② 等队列清空 + 线程结束(给宽限时间)
    pool.shutdownNow();                            // ③ 等不及了:强停并拿回未执行任务
    if (!pool.awaitTermination(30, TimeUnit.SECONDS))
        System.err.println("线程池未能终止,任务可能中断在途");  // ④ 终极兜底
}

2.10 扩展钩子与 ScheduledThreadPoolExecutor

三个 protected 钩子,子类可覆写(相当于给线程池加 AOP):

  • beforeExecute(Thread t, Runnable r):任务执行前------可做 MDC/TraceId 注入(把调用线程的上下文塞进 worker 线程);
  • afterExecute(Runnable r, Throwable t):任务执行后------无论任务抛不抛异常都会被调用,这是捕获 execute 任务异常做日志/指标的唯一正统位置(注意:submit 的 FutureTask 异常不经过这里抛,因为 FutureTask.run 内部吞了异常);
  • terminated():状态到 TERMINATED 前调用一次------可做池关闭后的资源清理、上报。

ScheduledThreadPoolExecutor extends ThreadPoolExecutor :覆写 execute/submit 全部转进 schedule;内部队列换成 DelayedWorkQueue(按触发时间出队);scheduleAtFixedRate(固定频率,上一次开始算)与 scheduleWithFixedDelay(固定延迟,上一次结束算)的核心差异在触发时间计算。覆写 onShutdown 逻辑保证关闭时取消并清空延迟任务。


3. 主线流程剖析 ★★★

3.1 提交与执行完整链路

#mermaid-svg-OXuefxNZ7Phvn02R{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OXuefxNZ7Phvn02R .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OXuefxNZ7Phvn02R .error-icon{fill:#552222;}#mermaid-svg-OXuefxNZ7Phvn02R .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OXuefxNZ7Phvn02R .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OXuefxNZ7Phvn02R .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OXuefxNZ7Phvn02R .marker.cross{stroke:#333333;}#mermaid-svg-OXuefxNZ7Phvn02R svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OXuefxNZ7Phvn02R p{margin:0;}#mermaid-svg-OXuefxNZ7Phvn02R .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OXuefxNZ7Phvn02R .cluster-label text{fill:#333;}#mermaid-svg-OXuefxNZ7Phvn02R .cluster-label span{color:#333;}#mermaid-svg-OXuefxNZ7Phvn02R .cluster-label span p{background-color:transparent;}#mermaid-svg-OXuefxNZ7Phvn02R .label text,#mermaid-svg-OXuefxNZ7Phvn02R span{fill:#333;color:#333;}#mermaid-svg-OXuefxNZ7Phvn02R .node rect,#mermaid-svg-OXuefxNZ7Phvn02R .node circle,#mermaid-svg-OXuefxNZ7Phvn02R .node ellipse,#mermaid-svg-OXuefxNZ7Phvn02R .node polygon,#mermaid-svg-OXuefxNZ7Phvn02R .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OXuefxNZ7Phvn02R .rough-node .label text,#mermaid-svg-OXuefxNZ7Phvn02R .node .label text,#mermaid-svg-OXuefxNZ7Phvn02R .image-shape .label,#mermaid-svg-OXuefxNZ7Phvn02R .icon-shape .label{text-anchor:middle;}#mermaid-svg-OXuefxNZ7Phvn02R .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OXuefxNZ7Phvn02R .rough-node .label,#mermaid-svg-OXuefxNZ7Phvn02R .node .label,#mermaid-svg-OXuefxNZ7Phvn02R .image-shape .label,#mermaid-svg-OXuefxNZ7Phvn02R .icon-shape .label{text-align:center;}#mermaid-svg-OXuefxNZ7Phvn02R .node.clickable{cursor:pointer;}#mermaid-svg-OXuefxNZ7Phvn02R .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OXuefxNZ7Phvn02R .arrowheadPath{fill:#333333;}#mermaid-svg-OXuefxNZ7Phvn02R .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OXuefxNZ7Phvn02R .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OXuefxNZ7Phvn02R .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OXuefxNZ7Phvn02R .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OXuefxNZ7Phvn02R .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OXuefxNZ7Phvn02R .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OXuefxNZ7Phvn02R .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OXuefxNZ7Phvn02R .cluster text{fill:#333;}#mermaid-svg-OXuefxNZ7Phvn02R .cluster span{color:#333;}#mermaid-svg-OXuefxNZ7Phvn02R div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OXuefxNZ7Phvn02R .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OXuefxNZ7Phvn02R rect.text{fill:none;stroke-width:0;}#mermaid-svg-OXuefxNZ7Phvn02R .icon-shape,#mermaid-svg-OXuefxNZ7Phvn02R .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OXuefxNZ7Phvn02R .icon-shape p,#mermaid-svg-OXuefxNZ7Phvn02R .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OXuefxNZ7Phvn02R .icon-shape .label rect,#mermaid-svg-OXuefxNZ7Phvn02R .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OXuefxNZ7Phvn02R .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OXuefxNZ7Phvn02R .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OXuefxNZ7Phvn02R :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是


池已关闭
池正常 但 workerCount==0
否 队列满
失败 达 max 或已关闭
抛异常
正常
有 getTask 取到
getTask 阻塞等待
超时回收
队列 take 到任务
调用 execute(task)
workerCount < corePoolSize ?
addWorker(task, core=true)
CAS 抢线程名额 → 创建 Worker → t.start()
runWorker 循环
池 RUNNING 且队列 offer 成功 ?
任务入队,recheck 二次检查
remove 出队 → reject 拒绝策略
补一个空转线程 addWorker(null, false)
addWorker(task, core=false) 扩非核心线程
reject → 拒绝策略
w.lock 打忙碌标记 → beforeExecute
task.run() 执行任务
afterExecute(task, ex) → 异常上抛 → 线程退出 → processWorkerExit 补员
afterExecute(task, null) → w.unlock
还有任务?
keepAlive 超时 / 池关闭?
CAS 递减名额 → runWorker 退出 → processWorkerExit

编号版主线(提交一个任务到线程池的完整旅程):

  1. execute 先看核心线程数是否满workerCountOf(c) < corePoolSize)------不满就 addWorker 建核心线程,任务作为 firstTask 直接开跑;
  2. 满了则 workQueue.offer(command) 入队(非阻塞,队列满立刻返回 false);
  3. 入队成功后 recheck :若期间池被关闭,remove 掉任务走拒绝策略;若池活着但没有任何线程(core=0 的池),补一个空转线程(addWorker(null, false))保证队列会被消费;
  4. 队列满则 addWorker(command, false) 扩非核心线程(上限 maximumPoolSize,核心参数 false);
  5. 扩不动(已满 max)→ 拒绝策略兜底;
  6. Worker 线程启动进 runWorker:先执行 firstTask,之后循环 getTask() 从队列取任务,take/poll 阻塞等待;
  7. 每执行一个任务前后打 w.lock()/w.unlock() 忙碌标记(配合 2.6 的中断策略);
  8. 队列取不到任务且超时(或池关闭)→ getTask 返回 null → 线程退出 → processWorkerExit 善后:从 workers 移除、累加 completedTaskCount、异常退出时补新线程tryTerminate 尝试推进状态机。

3.2 线程回收决策流程

#mermaid-svg-IrweSDANc3WCTgcP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IrweSDANc3WCTgcP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IrweSDANc3WCTgcP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IrweSDANc3WCTgcP .error-icon{fill:#552222;}#mermaid-svg-IrweSDANc3WCTgcP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IrweSDANc3WCTgcP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IrweSDANc3WCTgcP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IrweSDANc3WCTgcP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IrweSDANc3WCTgcP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IrweSDANc3WCTgcP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IrweSDANc3WCTgcP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IrweSDANc3WCTgcP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IrweSDANc3WCTgcP .marker.cross{stroke:#333333;}#mermaid-svg-IrweSDANc3WCTgcP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IrweSDANc3WCTgcP p{margin:0;}#mermaid-svg-IrweSDANc3WCTgcP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IrweSDANc3WCTgcP .cluster-label text{fill:#333;}#mermaid-svg-IrweSDANc3WCTgcP .cluster-label span{color:#333;}#mermaid-svg-IrweSDANc3WCTgcP .cluster-label span p{background-color:transparent;}#mermaid-svg-IrweSDANc3WCTgcP .label text,#mermaid-svg-IrweSDANc3WCTgcP span{fill:#333;color:#333;}#mermaid-svg-IrweSDANc3WCTgcP .node rect,#mermaid-svg-IrweSDANc3WCTgcP .node circle,#mermaid-svg-IrweSDANc3WCTgcP .node ellipse,#mermaid-svg-IrweSDANc3WCTgcP .node polygon,#mermaid-svg-IrweSDANc3WCTgcP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IrweSDANc3WCTgcP .rough-node .label text,#mermaid-svg-IrweSDANc3WCTgcP .node .label text,#mermaid-svg-IrweSDANc3WCTgcP .image-shape .label,#mermaid-svg-IrweSDANc3WCTgcP .icon-shape .label{text-anchor:middle;}#mermaid-svg-IrweSDANc3WCTgcP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IrweSDANc3WCTgcP .rough-node .label,#mermaid-svg-IrweSDANc3WCTgcP .node .label,#mermaid-svg-IrweSDANc3WCTgcP .image-shape .label,#mermaid-svg-IrweSDANc3WCTgcP .icon-shape .label{text-align:center;}#mermaid-svg-IrweSDANc3WCTgcP .node.clickable{cursor:pointer;}#mermaid-svg-IrweSDANc3WCTgcP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IrweSDANc3WCTgcP .arrowheadPath{fill:#333333;}#mermaid-svg-IrweSDANc3WCTgcP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IrweSDANc3WCTgcP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IrweSDANc3WCTgcP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IrweSDANc3WCTgcP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IrweSDANc3WCTgcP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IrweSDANc3WCTgcP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IrweSDANc3WCTgcP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IrweSDANc3WCTgcP .cluster text{fill:#333;}#mermaid-svg-IrweSDANc3WCTgcP .cluster span{color:#333;}#mermaid-svg-IrweSDANc3WCTgcP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IrweSDANc3WCTgcP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IrweSDANc3WCTgcP rect.text{fill:none;stroke-width:0;}#mermaid-svg-IrweSDANc3WCTgcP .icon-shape,#mermaid-svg-IrweSDANc3WCTgcP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IrweSDANc3WCTgcP .icon-shape p,#mermaid-svg-IrweSDANc3WCTgcP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IrweSDANc3WCTgcP .icon-shape .label rect,#mermaid-svg-IrweSDANc3WCTgcP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IrweSDANc3WCTgcP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IrweSDANc3WCTgcP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IrweSDANc3WCTgcP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

否 核心线程且不允许超时
是 计时线程
等到任务
超时 timedOut
否 只有我一个且队列还有活
是 可退
Worker 完成当前任务

再次 getTask()
池已 STOP 或 SHUTDOWN 且队列空 ?
CAS 名额-1 → 返回 null → 线程退出
allowCoreThreadTimeOut

或 wc > corePoolSize ?
workQueue.take() 无限期阻塞

(线程常驻)
workQueue.poll(keepAliveTime)

阻塞限时等待
返回任务 → 继续执行
wc > 1 或队列空 ?

3.3 关闭状态机流转

worker-2(空闲等队列) worker-1(执行任务中) 线程池 调用方 worker-2(空闲等队列) worker-1(执行任务中) 线程池 调用方 #mermaid-svg-o80cdsDVcrmxz0Dd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o80cdsDVcrmxz0Dd .error-icon{fill:#552222;}#mermaid-svg-o80cdsDVcrmxz0Dd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o80cdsDVcrmxz0Dd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o80cdsDVcrmxz0Dd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o80cdsDVcrmxz0Dd .marker.cross{stroke:#333333;}#mermaid-svg-o80cdsDVcrmxz0Dd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o80cdsDVcrmxz0Dd p{margin:0;}#mermaid-svg-o80cdsDVcrmxz0Dd .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o80cdsDVcrmxz0Dd text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-o80cdsDVcrmxz0Dd .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-o80cdsDVcrmxz0Dd .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-o80cdsDVcrmxz0Dd #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-o80cdsDVcrmxz0Dd .sequenceNumber{fill:white;}#mermaid-svg-o80cdsDVcrmxz0Dd #sequencenumber{fill:#333;}#mermaid-svg-o80cdsDVcrmxz0Dd #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-o80cdsDVcrmxz0Dd .messageText{fill:#333;stroke:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o80cdsDVcrmxz0Dd .labelText,#mermaid-svg-o80cdsDVcrmxz0Dd .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .loopText,#mermaid-svg-o80cdsDVcrmxz0Dd .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-o80cdsDVcrmxz0Dd .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-o80cdsDVcrmxz0Dd .noteText,#mermaid-svg-o80cdsDVcrmxz0Dd .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-o80cdsDVcrmxz0Dd .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o80cdsDVcrmxz0Dd .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o80cdsDVcrmxz0Dd .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o80cdsDVcrmxz0Dd .actorPopupMenu{position:absolute;}#mermaid-svg-o80cdsDVcrmxz0Dd .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-o80cdsDVcrmxz0Dd .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o80cdsDVcrmxz0Dd .actor-man circle,#mermaid-svg-o80cdsDVcrmxz0Dd line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-o80cdsDVcrmxz0Dd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 队列空 && workerCount==0 → 状态 → TIDYING → 执行 terminated() → TERMINATED alt 强制关闭 shutdown():状态 → SHUTDOWN interruptIdleWorkers:tryLock 成功才中断(W2 空闲 → 命中) getTask 被中断 → 检查状态 → SHUTDOWN 且队列空 → 退出 processWorkerExit → tryTerminate awaitTermination(60s) 返回 true shutdownNow():状态 → STOP,清空队列返回 List interruptIfStarted() 中断执行中的任务线程

3.4 一次带回收的完整时序

场景:core=2, max=4, 队列容量 2,主线程依次提交 7 个 1.5s 的任务:
任务队列(容量2) worker-4(非核心) worker-3(非核心) worker-2(核心) worker-1(核心) ThreadPoolExecutor 主线程 任务队列(容量2) worker-4(非核心) worker-3(非核心) worker-2(核心) worker-1(核心) ThreadPoolExecutor 主线程 #mermaid-svg-QlB0lyKtuQjBQZlA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QlB0lyKtuQjBQZlA .error-icon{fill:#552222;}#mermaid-svg-QlB0lyKtuQjBQZlA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QlB0lyKtuQjBQZlA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QlB0lyKtuQjBQZlA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QlB0lyKtuQjBQZlA .marker.cross{stroke:#333333;}#mermaid-svg-QlB0lyKtuQjBQZlA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QlB0lyKtuQjBQZlA p{margin:0;}#mermaid-svg-QlB0lyKtuQjBQZlA .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QlB0lyKtuQjBQZlA text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QlB0lyKtuQjBQZlA .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QlB0lyKtuQjBQZlA .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QlB0lyKtuQjBQZlA #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QlB0lyKtuQjBQZlA .sequenceNumber{fill:white;}#mermaid-svg-QlB0lyKtuQjBQZlA #sequencenumber{fill:#333;}#mermaid-svg-QlB0lyKtuQjBQZlA #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QlB0lyKtuQjBQZlA .messageText{fill:#333;stroke:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QlB0lyKtuQjBQZlA .labelText,#mermaid-svg-QlB0lyKtuQjBQZlA .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .loopText,#mermaid-svg-QlB0lyKtuQjBQZlA .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QlB0lyKtuQjBQZlA .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-QlB0lyKtuQjBQZlA .noteText,#mermaid-svg-QlB0lyKtuQjBQZlA .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-QlB0lyKtuQjBQZlA .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QlB0lyKtuQjBQZlA .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QlB0lyKtuQjBQZlA .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QlB0lyKtuQjBQZlA .actorPopupMenu{position:absolute;}#mermaid-svg-QlB0lyKtuQjBQZlA .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QlB0lyKtuQjBQZlA .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QlB0lyKtuQjBQZlA .actor-man circle,#mermaid-svg-QlB0lyKtuQjBQZlA line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-QlB0lyKtuQjBQZlA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 两线程各跑 1.5s 提交任务1 1 workerCount 0<2 → addWorker(核心) 执行 2 提交任务2 3 workerCount 1<2 → addWorker(核心) 执行 4 提交任务3 5 workerCount=2 不<core → 入队(3) 6 提交任务4 7 入队(3,4)队列已满 8 提交任务5 9 队列满 → addWorker(非核心) worker-3 执行 10 提交任务6 11 队列仍满 → worker-4 执行 12 提交任务7 13 满员且队列满 → AbortPolicy 抛 RejectedExecutionException 14 1.5s 后完成 → 从队列取任务3 15 完成 → 取任务4 16 完成 → 无任务 → poll(keepAlive) 超时 → 回收退出 17


4. 类关系图 ★★★

4.1 继承体系全景

#mermaid-svg-KTfhhESd5oKJdZ6F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KTfhhESd5oKJdZ6F .error-icon{fill:#552222;}#mermaid-svg-KTfhhESd5oKJdZ6F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KTfhhESd5oKJdZ6F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KTfhhESd5oKJdZ6F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KTfhhESd5oKJdZ6F .marker.cross{stroke:#333333;}#mermaid-svg-KTfhhESd5oKJdZ6F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KTfhhESd5oKJdZ6F p{margin:0;}#mermaid-svg-KTfhhESd5oKJdZ6F g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-KTfhhESd5oKJdZ6F g.classGroup text .title{font-weight:bolder;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster-label text{fill:#333;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster-label span{color:#333;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster-label span p{background-color:transparent;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster text{fill:#333;}#mermaid-svg-KTfhhESd5oKJdZ6F .cluster span{color:#333;}#mermaid-svg-KTfhhESd5oKJdZ6F .nodeLabel,#mermaid-svg-KTfhhESd5oKJdZ6F .edgeLabel{color:#131300;}#mermaid-svg-KTfhhESd5oKJdZ6F .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-KTfhhESd5oKJdZ6F .label text{fill:#131300;}#mermaid-svg-KTfhhESd5oKJdZ6F .labelBkg{background:#ECECFF;}#mermaid-svg-KTfhhESd5oKJdZ6F .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-KTfhhESd5oKJdZ6F .classTitle{font-weight:bolder;}#mermaid-svg-KTfhhESd5oKJdZ6F .node rect,#mermaid-svg-KTfhhESd5oKJdZ6F .node circle,#mermaid-svg-KTfhhESd5oKJdZ6F .node ellipse,#mermaid-svg-KTfhhESd5oKJdZ6F .node polygon,#mermaid-svg-KTfhhESd5oKJdZ6F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KTfhhESd5oKJdZ6F .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F g.clickable{cursor:pointer;}#mermaid-svg-KTfhhESd5oKJdZ6F g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-KTfhhESd5oKJdZ6F g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-KTfhhESd5oKJdZ6F .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-KTfhhESd5oKJdZ6F .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-KTfhhESd5oKJdZ6F .dashed-line{stroke-dasharray:3;}#mermaid-svg-KTfhhESd5oKJdZ6F .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-KTfhhESd5oKJdZ6F #compositionStart,#mermaid-svg-KTfhhESd5oKJdZ6F .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #compositionEnd,#mermaid-svg-KTfhhESd5oKJdZ6F .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #dependencyStart,#mermaid-svg-KTfhhESd5oKJdZ6F .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #dependencyStart,#mermaid-svg-KTfhhESd5oKJdZ6F .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #extensionStart,#mermaid-svg-KTfhhESd5oKJdZ6F .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #extensionEnd,#mermaid-svg-KTfhhESd5oKJdZ6F .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #aggregationStart,#mermaid-svg-KTfhhESd5oKJdZ6F .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #aggregationEnd,#mermaid-svg-KTfhhESd5oKJdZ6F .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #lollipopStart,#mermaid-svg-KTfhhESd5oKJdZ6F .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F #lollipopEnd,#mermaid-svg-KTfhhESd5oKJdZ6F .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-KTfhhESd5oKJdZ6F .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-KTfhhESd5oKJdZ6F .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KTfhhESd5oKJdZ6F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KTfhhESd5oKJdZ6F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KTfhhESd5oKJdZ6F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 继承(不可重入独占锁)
实现
返回内部包装类
工作窃取(注意不是 TPE)
<<interface>>
Executor
+execute(Runnable)
<<interface>>
ExecutorService
+shutdown()
+shutdownNow()
+submit(Callable)
+invokeAll(Collection)
AbstractExecutorService
+submit(Runnable)
+invokeAll(Collection)
ThreadPoolExecutor
-AtomicInteger ctl
-BlockingQueue workQueue
-HashSet workers
+setCorePoolSize(int)
+setMaximumPoolSize(int)
+setKeepAliveTime(long, TimeUnit)
+allowCoreThreadTimeOut(boolean)
#beforeExecute(Thread, Runnable)
#afterExecute(Runnable, Throwable)
#terminated()
ScheduledThreadPoolExecutor
+schedule(Runnable, long, TimeUnit)
+scheduleAtFixedRate(Runnable, long, long, TimeUnit)
Worker
+Thread thread
+Runnable firstTask
-volatile long completedTasks
AbstractQueuedSynchronizer
Runnable
<<工厂工具类>>
Executors
+newFixedThreadPool(int)
+newCachedThreadPool()
+newSingleThreadExecutor()
+newScheduledThreadPool(int)
+newWorkStealingPool()
ForkJoinPool

4.2 内部对象关系

#mermaid-svg-UaO0pm4BLHy97itY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UaO0pm4BLHy97itY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UaO0pm4BLHy97itY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UaO0pm4BLHy97itY .error-icon{fill:#552222;}#mermaid-svg-UaO0pm4BLHy97itY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UaO0pm4BLHy97itY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UaO0pm4BLHy97itY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UaO0pm4BLHy97itY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UaO0pm4BLHy97itY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UaO0pm4BLHy97itY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UaO0pm4BLHy97itY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UaO0pm4BLHy97itY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UaO0pm4BLHy97itY .marker.cross{stroke:#333333;}#mermaid-svg-UaO0pm4BLHy97itY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UaO0pm4BLHy97itY p{margin:0;}#mermaid-svg-UaO0pm4BLHy97itY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UaO0pm4BLHy97itY .cluster-label text{fill:#333;}#mermaid-svg-UaO0pm4BLHy97itY .cluster-label span{color:#333;}#mermaid-svg-UaO0pm4BLHy97itY .cluster-label span p{background-color:transparent;}#mermaid-svg-UaO0pm4BLHy97itY .label text,#mermaid-svg-UaO0pm4BLHy97itY span{fill:#333;color:#333;}#mermaid-svg-UaO0pm4BLHy97itY .node rect,#mermaid-svg-UaO0pm4BLHy97itY .node circle,#mermaid-svg-UaO0pm4BLHy97itY .node ellipse,#mermaid-svg-UaO0pm4BLHy97itY .node polygon,#mermaid-svg-UaO0pm4BLHy97itY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UaO0pm4BLHy97itY .rough-node .label text,#mermaid-svg-UaO0pm4BLHy97itY .node .label text,#mermaid-svg-UaO0pm4BLHy97itY .image-shape .label,#mermaid-svg-UaO0pm4BLHy97itY .icon-shape .label{text-anchor:middle;}#mermaid-svg-UaO0pm4BLHy97itY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UaO0pm4BLHy97itY .rough-node .label,#mermaid-svg-UaO0pm4BLHy97itY .node .label,#mermaid-svg-UaO0pm4BLHy97itY .image-shape .label,#mermaid-svg-UaO0pm4BLHy97itY .icon-shape .label{text-align:center;}#mermaid-svg-UaO0pm4BLHy97itY .node.clickable{cursor:pointer;}#mermaid-svg-UaO0pm4BLHy97itY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UaO0pm4BLHy97itY .arrowheadPath{fill:#333333;}#mermaid-svg-UaO0pm4BLHy97itY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UaO0pm4BLHy97itY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UaO0pm4BLHy97itY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UaO0pm4BLHy97itY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UaO0pm4BLHy97itY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UaO0pm4BLHy97itY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UaO0pm4BLHy97itY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UaO0pm4BLHy97itY .cluster text{fill:#333;}#mermaid-svg-UaO0pm4BLHy97itY .cluster span{color:#333;}#mermaid-svg-UaO0pm4BLHy97itY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UaO0pm4BLHy97itY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UaO0pm4BLHy97itY rect.text{fill:none;stroke-width:0;}#mermaid-svg-UaO0pm4BLHy97itY .icon-shape,#mermaid-svg-UaO0pm4BLHy97itY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UaO0pm4BLHy97itY .icon-shape p,#mermaid-svg-UaO0pm4BLHy97itY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UaO0pm4BLHy97itY .icon-shape .label rect,#mermaid-svg-UaO0pm4BLHy97itY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UaO0pm4BLHy97itY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UaO0pm4BLHy97itY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UaO0pm4BLHy97itY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ThreadPoolExecutor
取任务
决定是否新建/回收
Worker × N
thread(真实 OS 线程)
AQS state

-1 未启动 / 0 空闲 / 1 执行中
ctl AtomicInteger

高3位 runState + 低29位 workerCount
workQueue 任务队列
workers HashSet 线程集合
mainLock 主锁

保护 workers 与状态迁移


5. 参数调优实战 ★★★

5.1 七大参数角色与调优影响

参数 语义 调小/调大的代价 运行时可否修改
corePoolSize 核心线程数 常驻线程池下限 小→吞吐受限;大→常驻资源占用 setCorePoolSize(调大补线程、调小中断空闲核心)
maximumPoolSize 最大线程数 队列满时扩到的上限 小→易触发拒绝;大→线程暴涨风险 setMaximumPoolSize(调小后超出的空闲线程会被回收
workQueue 任务队列 core 与 max 之间的蓄水池 见 5.3 ❌ 构造后不可换(需换队列只能重建或自研包装)
keepAliveTime 空闲存活 非核心线程(或允许超时的核心)空闲多久回收 小→线程频繁创建销毁;大→空闲占资源 setKeepAliveTime(非核心立即生效)
threadFactory 线程工厂 线程命名、daemon、优先级 不命名 = 排查问题时"pool-N-thread-M"毫无业务线索
handler 拒绝策略 满载或关闭时的兜底 见 5.4 setRejectedExecutionHandler
allowCoreThreadTimeOut 隐式参数 核心线程是否也可超时回收 true 后线程数可降到 0 allowCoreThreadTimeOut(true)(要求 keepAliveTime > 0)

5.2 核心线程数估算公式 ★

理论基准公式(线程利用率 U 与阻塞比 W/C 决定):

线程数 = Ncpu × U × (1 + W / C)

其中 W = 平均等待(IO/锁阻塞)时间,C = 平均计算时间

工程近似:

任务类型 判据 经验值
CPU 密集 几乎不阻塞(纯计算/压缩/加密) Ncpu + 1(多 1 个补偿页缺失等停顿)
IO 密集 网络/RPC/DB/文件占大头 Ncpu × 2 ~ Ncpu × 3 (粗粒度)或 Ncpu / (1 - 阻塞系数)(阻塞系数 0.8~0.9 时 ≈ Ncpu × 5~10)
java 复制代码
// 获取可用核数(注意容器环境:K8s 未设置 limits 时读到的可能是物理机核数!)
int ncpu = Runtime.getRuntime().availableProcessors();

⚠️ 公式只能给起点,线上必须"先压测再微调" :设置理论值 → 压测观察(CPU 利用率、队列水位、RT 分位线)→ 按 5.6 动态调整。纯 IO 任务的真实最优线程数常远超公式(如 RPC 等待 100ms、计算 1ms 时 Ncpu×100 也能扛住),因为瓶颈在 IO 不在 CPU------调优目标是"别让线程成为瓶颈,也别让 CPU/内存被打爆",而不是追求某个公式

容器场景注意:availableProcessors() 受 cgroup 影响(JDK 10+ 已感知容器配额),但未设 CPU limits 时仍可能虚高;可结合 -XX:ActiveProcessorCount 显式指定。

5.3 队列容量与峰值兜底

队列容量没有银弹公式,先想清楚两件事

  1. 你允许任务最多排队多久(容忍延迟 T):队列容量 ≥ 峰值入队速率 P × 容忍延迟 T;
  2. 队列满之后的动作 :满了才会动 max 线程、再满才拒绝------队列越大,max 越难触发,拒绝越晚,但任务延迟与积压内存越大
java 复制代码
// 估算示例:接口峰值 500 req/s,允许单任务最多等 2s → 队列至少 1000
// 经验线:LinkedBlockingQueue 容量常取 1000~10000(视任务体量与内存),
// 而不是 Executors 的"无界"(= 无限积压)
int queueCapacity = 1000;   // ≈ 峰值速率 × 容忍排队秒数

推荐组合策略(美团《Java线程池实现原理及其在美团业务中的实践》的思路):

水位 线程数取值 语义
平时 core = 平均负载下稳定需要的线程数 常态运行成本最小
瞬时尖峰 queue 吸收一部分(容量有限) 削峰
持续积压 max = 能扛住的最坏峰值(结合 CPU/内存预算) 兜底扩线
全满 拒绝策略 + 告警 限流信号

5.4 拒绝策略与业务选型

场景 推荐
异步任务丢了也无所谓(日志、埋点) 包装 DiscardPolicy + 打点计数
希望上游感知负载(经典 RPC/Web 场景) CallerRunsPolicy:调用线程亲自执行 = 自然反压;或 Abort + 调用方 catch 降级
必须报错不能让调用方蒙在鼓里 AbortPolicy(默认),配合统一异常包装
队列里旧任务可牺牲(行情推送只保留最新) DiscardOldestPolicy

拒绝不等于结束:生产做法通常是 handler 里记录"拒绝快照"(此刻 core/max/队列大小/活跃线程数)→ 发告警 → 再按策略执行。拒绝是系统过载的信号,不是吞任务的借口。

5.5 线程工厂与异常兜底

线程命名是排障第一生产力,自定义 ThreadFactory 两行搞定:

java 复制代码
ThreadFactory bizFactory = new ThreadFactory() {
    private final AtomicInteger seq = new AtomicInteger(1);
    @Override public Thread newThread(Runnable r) {
        Thread t = new Thread(r, "order-async-" + seq.getAndIncrement());  // 业务前缀
        t.setDaemon(false);              // 默认非守护,进程退出前会等待
        t.setUncaughtExceptionHandler((th, e) ->          // 线程级兜底日志
            log.error("线程 {} 意外崩溃", th.getName(), e));
        return t;
    }
};

任务异常两条防线(execute 提交的任务异常默认不会打到业务日志,必须自己兜):

java 复制代码
// 防线1:覆写 afterExecute(execute 与 submit 都必经,唯一正统打点位置)
@Override protected void afterExecute(Runnable r, Throwable t) {
    // submit 的异常被 FutureTask 吞了,需要这样取回
    if (t == null && r instanceof Future<?> f) {
        try { f.get(0, TimeUnit.NANOSECONDS); }
        catch (ExecutionException e) { t = e.getCause(); }
        catch (Exception ignored) {}
    }
    if (t != null) log.error("任务执行异常", t);
}
// 防线2:业务代码自己 try-catch(最可靠,别指望框架)

5.6 动态调参思路 ★

线程池参数不需要重启就能改(这正是 ThreadPoolExecutor 比大多数自研池高级的地方),生产调优应是"运行中观察 → 运行中微调"的闭环:

动作 触发 内部实现
setCorePoolSize(n) 调大 尝试 addWorker(null) 补线程直到 n
setCorePoolSize(n) 调小 interruptIdleWorkers() 逐出空闲核心线程(正在跑的不动)
setMaximumPoolSize(n) 调小(小于当前线程数) 超出的线程在 getTask 中被判定 wc > maximumPoolSize 自行退出
setKeepAliveTime(t) 任意时刻 立即影响下一次 poll 超时判定
allowCoreThreadTimeOut(true) 核心也想回收 核心线程转计时回收,线程数可降到 0
setRejectedExecutionHandler(h) 调整过载行为 直接生效

配套监控四板斧(动态调参的依据):

java 复制代码
// ① 内部指标快照
int poolSize      = pool.getPoolSize();            // 当前线程数
int active        = pool.getActiveCount();         // 正在执行任务的线程数
long queueSize    = pool.getQueue().size();        // 排队任务数(核心水位指标)
long largest      = pool.getLargestPoolSize();     // 历史最大线程数(峰值证据)
long taskCount    = pool.getTaskCount();           // 已提交任务总数
long completed    = pool.getCompletedTaskCount();  // 已完成数
double activeRate = (double) active / poolSize;    // 忙碌率:连续 >0.8 且队列涨 → 调大;长期 <0.5 → 调小

② 运维层:Arthas threadpool 命令可直接查看/修改 JVM 内线程池参数(热更新场景神器);③ Spring Boot 场景暴露 actuator 线程池指标或用 ThreadPoolTaskExecutor 统一托管;④ 告警阈值:队列水位 > 80%、拒绝次数 > 0、活跃线程 == max 持续 N 分钟。

5.7 可运行 Demo 1 提交顺序实证

用日志证据复现"core → queue → max → reject"四段扩容,跑完就懂参数含义。

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class TpeOrderDemo {
    static final AtomicInteger seq = new AtomicInteger(0);
    static long t0 = System.currentTimeMillis();

    public static void main(String[] args) throws Exception {
        // core=2 max=4,队列容量 2
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, 4, 1, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2),
                r -> new Thread(r, "order-worker-" + seq.getAndIncrement()),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable job = () -> {
            String name = Thread.currentThread().getName();
            System.out.printf("[%4dms] %s 开始执行(队列=%d 线程数=%d)%n",
                    System.currentTimeMillis() - t0, name,
                    pool.getQueue().size(), pool.getPoolSize());
            sleep(1500);                                  // 模拟业务耗时,保持 worker 忙碌
            System.out.printf("[%4dms] %s 完成%n", System.currentTimeMillis() - t0, name);
        };

        submitAndNote(pool, job, 1);                      // ① 核心1 诞生
        submitAndNote(pool, job, 2);                      // ② 核心2 诞生
        sleep(150);                                       // 让 1、2 都在跑(1.5s 内)
        submitAndNote(pool, job, 3);                      // ③ 核心满 → 入队(队列 1/2)
        submitAndNote(pool, job, 4);                      // ④ 入队(队列 2/2 满)
        sleep(150);
        submitAndNote(pool, job, 5);                      // ⑤ 队列满 → 扩 worker-3(非核心)
        submitAndNote(pool, job, 6);                      // ⑥ 队列仍满 → 扩 worker-4(非核心,达 max)
        try {
            submitAndNote(pool, job, 7);                  // ⑦ 满员+队列满 → 拒绝!
        } catch (RejectedExecutionException e) {
            System.out.printf("[%4dms] ❌ 任务7 被拒绝:%s%n", System.currentTimeMillis() - t0,
                    e.getClass().getSimpleName());
        }
        pool.shutdown();
        pool.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("========= 观察结论 =========");
        System.out.println("1、2 由核心线程执行;3、4 先排队(没有任何新线程出现);");
        System.out.println("5、6 才触发非核心扩线到 max=4;7 在满员+队列满时被 AbortPolicy 拒绝。");
    }

    static void submitAndNote(ThreadPoolExecutor pool, Runnable job, int no) {
        System.out.printf("[%4dms] 提交任务%d%n", System.currentTimeMillis() - t0, no);
        pool.execute(job);
    }
    static void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
}

预期输出形态(时间戳略):

text 复制代码
[   0ms] 提交任务1
[   1ms] order-worker-1 开始执行(队列=0 线程数=1)
[   2ms] 提交任务2
[   3ms] order-worker-2 开始执行(队列=0 线程数=2)
[ 152ms] 提交任务3
[ 153ms] 提交任务4
[ 302ms] 提交任务5
[ 302ms] order-worker-3 开始执行(队列=2 线程数=3)   ← 5 没排队,直接扩线
[ 303ms] 提交任务6
[ 303ms] order-worker-4 开始执行(队列=2 线程数=4)   ← 6 扩到 max
[ 305ms] 提交任务7
[ 305ms] ❌ 任务7 被拒绝:RejectedExecutionException
[1500ms+] order-worker-1 完成 → 取走队列中的任务3 ...

5.8 可运行 Demo 2 超时回收与动态调整

演示非核心线程 keepAlive 回收、核心线程 allowCoreThreadTimeOut 后也能归零、以及 setMaximumPoolSize 运行时缩编。

java 复制代码
import java.util.concurrent.*;

public class TpeRecycleDemo {
    static long t0 = System.currentTimeMillis();
    static void p(String s) { System.out.printf("[%4dms] %s%n", System.currentTimeMillis() - t0, s); }

    public static void main(String[] args) throws Exception {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1, 3, 1, TimeUnit.SECONDS,                 // core=1 max=3 keepAlive=1s
                new ArrayBlockingQueue<>(1));              // 有界队列容量 1 → 逼出非核心线程
        pool.allowCoreThreadTimeOut(true);                 // ★ 核心线程也允许超时回收

        for (int i = 1; i <= 3; i++) {                     // 三个任务 0.8s:1 进核心、2 排队、3 逼队列满扩线
            pool.execute(() -> sleep(800));
            sleep(60);
        }
        p("提交后:poolSize=" + pool.getPoolSize() + "(应为 3:1核心+2非核心)");
        sleep(2000);                                        // 任务全部完成,keepAlive=1s 已过
        p("超时后:poolSize=" + pool.getPoolSize() + "(应为 0:非核心先回收,核心因 allowCoreThreadTimeOut 也回收)");
        p("largestPoolSize=" + pool.getLargestPoolSize() + "(历史峰值 3,监控要点)");

        // ------ 动态调参演示:重新提交任务后直接运行时缩编 ------
        for (int i = 1; i <= 3; i++) { pool.execute(() -> sleep(1200)); sleep(60); }
        p("二次提交后:poolSize=" + pool.getPoolSize() + "(3)");
        pool.setMaximumPoolSize(1);                         // ★ 运行时把上限调到 1:超出的 2 个空闲线程将被回收
        p("setMaximumPoolSize(1) 后立即:poolSize=" + pool.getPoolSize()
                + "(空闲非核心线程在 getTask 下一轮判定退出)");
        sleep(2000);
        p("两秒后:poolSize=" + pool.getPoolSize() + "(回收至 1 个核心内)");

        pool.shutdownNow();
    }
    static void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
}

预期输出3 → 0 → 3 →(setMaximumPoolSize(1))→ 1,完整展示超时回收与动态缩编两条回收路径。

5.9 生产环境模板代码

直接抄改的生产级配置(Spring Boot 场景可套进 ThreadPoolTaskExecutor,Bean 销毁时 @PreDestroy 自动触发优雅停机):

java 复制代码
public ThreadPoolExecutor buildBizPool() {
    int ncpu = Runtime.getRuntime().availableProcessors();
    // 命名工厂 + 非守护 + 崩溃兜底日志
    ThreadFactory factory = new ThreadFactory() {
        private final AtomicInteger seq = new AtomicInteger(1);
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "biz-async-" + seq.getAndIncrement());
            t.setUncaughtExceptionHandler((th, e) ->
                    log.error("线程池线程崩溃: {}", th.getName(), e));
            return t;
        }
    };
    ThreadPoolExecutor pool = new ThreadPoolExecutor(
            ncpu * 2,                    // core:IO 密集先按 2×CPU 起步
            ncpu * 4,                    // max:压测后按峰值微调
            60, TimeUnit.SECONDS,        // 空闲回收
            new ArrayBlockingQueue<>(1000),   // 有界!容量 = 峰值速率 × 容忍排队时间
            factory,
            new ThreadPoolExecutor.CallerRunsPolicy());   // 背压兜底

    // 全参数动态化入口:监控系统/管理接口可随时改
    DynamicPoolRegistry.register("biz-async", pool);   // 暴露 setXxx + 指标快照
    return pool;
}

// 优雅停机(Spring 用 @PreDestroy / DisposableBean / SmartLifecycle#stop 统一触发)
public void shutdownGracefully() {
    pool.shutdown();                                     // ① 不再接新活
    try {
        if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
            pool.shutdownNow();                          // ② 宽限 30s 后强停
            pool.awaitTermination(5, TimeUnit.SECONDS);  // ③ 最后再等 5s
        }
    } catch (InterruptedException e) {
        pool.shutdownNow();
        Thread.currentThread().interrupt();              // 恢复中断位
    }
}

完整方案还应包含:任务里放 TraceId/业务上下文 (beforeExecute 注入 MDC,afterExecute 清理,防止串号)、afterExecute 打点(执行时长、异常率)、队列水位告警、以及 hook 到发版平台的重建流程(大版本升级时优雅停机 + 新池预热,避免停机窗口)。


6. 扩展点与常见问题 FAQ ★★★

6.1 高频面试问答

Q1:execute 的完整处理顺序?

core 未满先开核心线程直接跑;满了入队;队列满扩非核心线程到 max;满员且队列满走拒绝策略。队列不满时绝不会新建非核心线程

Q2:为什么 core 满了先进队列而不是直接扩 max?

平滑资源使用:队列吸收瞬时尖峰,线程数稳定在 core 附近避免频繁建/毁线程;持续积压才逐级扩到 max。若"任务来了就扩线",线程数会随流量剧烈震荡,扩线成本(创建、上下文切换、内存)反而成为瓶颈。

Q3:非核心线程怎么被回收?

getTask 里 timed = allowCoreThreadTimeOut || wc > corePoolSize:非核心线程走 workQueue.poll(keepAliveTime) 限时阻塞,超时没等到任务 → 下一轮判定 timed && timedOut → CAS 名额-1 退出。队列还有任务时保底留 1 个线程。

Q4:shutdown 与 shutdownNow 的区别?

shutdown → SHUTDOWN:拒绝新任务、只中断空闲 线程,正在执行与队列任务都跑完;shutdownNow → STOP:清空队列(返回未执行任务 List)、中断所有已启动线程,正在执行的任务收到 interrupt。配合 awaitTermination 才能确定终态。

Q5:execute 的任务抛异常会怎样?

该 worker 线程直接退出(runWorker 异常上抛 → completedAbruptly → processWorkerExit),池检测到异常退出会自动补一个新线程 ;异常本身默认不记录(可用 afterExecute 钩子统一兜底)。submit 则不同 :异常被 FutureTask 捕获,Future.get() 抛 ExecutionException,线程不退出。

Q6:核心线程数怎么定?

CPU 密集 ≈ Ncpu+1;IO 密集 ≈ Ncpu×2 起步或按 Ncpu/(1-阻塞系数);公式只给起点,必须压测 + 动态调优(5.2/5.6)。容器环境注意 CPU 配额感知问题。

Q7:为什么不建议 Executors 工厂方法?

Fixed/Single 用无界队列:任务积压不拒绝、内存可被拖垮(OOM),且 max 形同虚设;Cached 的 max 为 Integer.MAX_VALUE:突发流量线程暴涨打垮系统。阿里规约强制手写 ThreadPoolExecutor。

Q8:核心线程能回收吗?线程数能到 0 吗?

allowCoreThreadTimeOut(true)(要求 keepAliveTime>0)后核心线程同样超时回收,线程数可降到 0(队列来了任务才懒创建)。适合"白天高峰晚上闲"的场景省资源。

Q9:如何优雅停机?

shutdown → awaitTermination(宽限) → 超时 shutdownNow → 再 awaitTermination 兜底 → 恢复中断位。Spring 场景交给容器生命周期(@PreDestroy/SmartLifecycle),另可注册 JVM shutdown hook 兜底。

Q10:怎么监控线程池?

内部指标:getPoolSize/getActiveCount/getQueue().size()/getLargestPoolSize/getTaskCount/getCompletedTaskCount;活跃率 = active/poolSize(>0.8 且队列涨说明要扩容)。工具:Arthas threadpool 命令可看可改;Spring 用 actuator + ThreadPoolTaskExecutor 托管;关键告警:队列水位、拒绝次数。

Q11:单线程池里两个任务先后提交,能保证按序执行吗?

newSingleThreadExecutor 保证(1 个 worker 顺序取任务),但注意:任务里异常退出后补的线程继续执行后续任务,顺序仍保持;而"先提交的任务被拒绝后由调用者执行"(CallerRuns)会打乱顺序------细节场景需自行设计。

Q12:线程池与虚拟线程(JDK 21+)什么关系?

虚拟线程的定位是"一任务一线程、由 JVM 调度海量轻量线程",大量 IO 阻塞场景不再需要池化复用;但 ThreadPoolExecutor 仍是 ExecutorService 的标准实现、可控并发上限的通用设施(虚拟线程也可配固定调度器实现限流池化),二者互补而非替代,平台线程池代码基本不受影响。

6.2 源码细节点辨析

D1:为什么把 runState 和 workerCount 打包装进一个 AtomicInteger?

两个字段若分开,要么加锁才能保证"改状态同时改线程数"的原子性(如 shutdown 瞬间新 worker 冒出来),要么出现读不一致。打包后用 compareAndIncrementWorkerCount 等 CAS 一次原子改两量,所有快照 ctl.get() 读一次即一致。取 29 位低位的 workerCount 上限 536870911 实际不可达,无约束意义。

D2:execute 里二次检查 recheck 到底防什么?

防"offer 成功与最终判定之间线程池被 shutdown/shutdownNow 的竞态":入队后若池已关闭,任务可能永远不被执行且调用方毫不知情,所以 remove(command) 取回 → 走拒绝策略,保证每个任务都有明确归宿(执行 or 拒绝)。

D3:addWorker 的 rs == SHUTDOWN && firstTask == null 放行条件怎么理解?

SHUTDOWN 状态队列里可能还有任务,但不允许新建线程执行新任务------唯一例外是补"空转线程"(firstTask=null)去消费队列里剩余任务。这是"优雅关停不丢队列任务"的最后一块拼图(与 getTask 中 SHUTDOWN+队列空则退出的逻辑呼应)。

D4:Worker 初始 state=-1 的深意?

runWorker 第一行 w.unlock() 把 -1 置 0。在此之前 interruptIfStarted() 判断 getState() >= 0 不成立 → shutdownNow 也不会中断这个"还没 start 完"的线程;之后线程才算"可中断"。而 runWorker 每任务 w.lock() 让 AQS state 变 1,interruptIdleWorkersw.tryLock() 拿不到锁 → 正在执行任务的核心不会被 shutdown 误伤。

D5:任务异常退出后补员规则的精确定义?

processWorkerExit 中:completedAbruptly=true(异常退出)→ 无条件尝试 addWorker(null, false) 补一个;正常退出 → 按 allowCoreThreadTimeOut ? 0 : corePoolSize(队列非空时保底 1)决定是否补。所以"execute 抛异常"的线程虽然死了,但池的容量机制会自愈。

D6:corePoolSize=0 的池(CachedThreadPool)任务为什么能狂飙线程?

execute 第一段 workerCount < 0 恒不成立;第二段队列 offer:SynchronousQueue 无缓冲,只有恰好有线程在 take 时才成功 ------没有空闲线程时 offer 必然失败 → 走第三段 addWorker(false) 新建线程 → 直到 max(Cached 的 max 是 Integer.MAX_VALUE)。线程完成后 poll 60s 超时回收。即:任务来多少开多少,全靠"无线程在等"自然触发扩线

D7:SHUTDOWN 下阻塞在 take 的 worker 怎么醒来?

shutdown() 的 interruptIdleWorkers 中断空闲 worker → getTask 的 catch (InterruptedException retry) 把 timedOut 置 false → 重新循环 → rs >= SHUTDOWN && workQueue.isEmpty()(队列已被消费空)→ 名额-1 返回 null 退出;若队列还有任务则继续 take 消费------循环保证"任务没跑完线程不死"。

D8:FutureTask 在 runWorker 里的特殊性?

submit 返回的 FutureTask 在 task.run() 时内部 catch 了所有异常存入 outcome,因此 runWorker 的 catch 分支不会 触发(afterExecute 的 Throwable 参数是 null)------这也是很多人 afterExecute 里看不到 submit 任务异常的原因,需要用 future.get() 主动取回(见 5.5 防线 1 代码)。

6.3 总结与学习路径

一句话复习

ThreadPoolExecutor 是一个用 ctl 打包状态与线程数、用 Worker(AQS 锁当忙碌标志)循环执行、用队列缓冲削峰 的可复用线程管家。扩容顺序 core → queue → max → reject 是它全部行为的钥匙;回收靠 getTask 的"限时阻塞 + 名额让渡";关闭靠状态机 SHUTDOWN/STOP + tryTerminate 的协作推进。调优没有银弹公式------先按 5.2 起步,再按监控动态调,永远让拒绝策略当你的过载报警器

上手路径建议:先跑 Demo 1 亲眼看到四段扩容(10 分钟)→ 对照 3.1 流程图读 execute/addWorker/runWorker 源码 → 用 Demo 2 验证回收与动态调整 → 看 shutdown 家族源码确认关闭语义 → 回 6.1 自测问答。动态调参进阶参考美团工程实践《Java线程池实现原理及其在美团业务中的实践》;虚拟线程对池化的冲击另见相关笔记。

相关推荐
抠脚小弟2 小时前
WebSocketServer 使用详解:从入门到实战
java·spring·websocketserver
随便做点啥2 小时前
32卡64G-910B4-16后端集群部署报告
java·大数据·运维
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之字段验证与自定义验证注解实战
java·算法·spring
CodeStats2 小时前
【Java 类加载器】Java 类加载器完整体系深度拆解(上):从 JVM 启动到双亲委派模型
java·开发语言·jvm·classloader·双亲委派
带多刺的玫瑰2 小时前
Leecode#29刷题之两数相除
java·python·算法
LuTshoes3 小时前
spring ai 实战-MCP
java·人工智能·spring
CodeStats3 小时前
【Java类加载器】Java 类加载器完整体系深度拆解(下):自定义加载器实战与 SPI 破坏双亲委派(MySQL 驱动揭秘)
java·开发语言·jvm·classloader·双亲委派
dishugj3 小时前
操作系统四大特征|软考架构师考点梳理
java·linux·服务器
砚底藏山河3 小时前
并发与限频工程:把20只的2秒压到0.5秒不封号(魔码量化实战 #03)
java·开发语言·数据库·python·金融