我们继续深入到 ThreadPoolExecutor 的内部运行机制,彻底搞清楚:
- 任务提交后线程池到底怎么处理?
- 线程池内部有哪些状态?
- ctl 是如何用 32 位整数保存"线程池状态 + 线程数量"的?
- shutdown()、shutdownNow() 区别到底是什么?
- worker 是怎么管理线程的?
理解这篇,你就真正掌握了 Java 线程池的灵魂。
一、线程池处理任务的完整流程(最重要的知识点)
当你执行:
executor.execute(task);
线程池会按照 3 个阶段 处理任务。
我给你画一张清晰流程图👇(重点)
① 有空闲核心线程?→ 直接创建 Worker 执行任务
② 核心线程满了 → 放入队列
③ 队列也放不下 → 尝试创建"非核心线程(临时线程)"
① 有空闲核心线程?→ 直接创建 Worker 执行任务
java
if (workerCount < corePoolSize) {
创建核心线程执行任务
return
}
核心线程数不足 → 创建新的核心线程。
② 核心线程满了 → 放入队列
java
if (workQueue.offer(task)) {
// 队列接收成功
}
- 队列可以无限放(LinkedBlockingQueue)
- 或有界(ArrayBlockingQueue)
如果队列能放下 → 线程池不会增加线程
③ 队列也放不下 → 尝试创建"非核心线程(临时线程)"
java
if (workerCount < maximumPoolSize) {
创建非核心线程执行任务
return
}
如果还不行 → 线程池触发 拒绝策略
二、线程池的五大状态(必须背下来)
线程池状态非常关键,比如**shutdown() 和 shutdownNow()**的区别就来自这里。
线程池有 五种状态(按优先级由大到小):
1)RUNNING
- 接收新任务
- 执行队列任务
正常工作状态。
2)SHUTDOWN
- 不再接收新任务
- 继续执行队列中的任务
调用:
executor.shutdown();
进入 SHUTDOWN。
3)STOP
- 不接收新任务
- 不执行队列任务
- 中断正在执行的线程
调用:
executor.shutdownNow();
线程池进入 STOP。
4)TIDYING
- 所有任务已终止
- workerCount == 0
- 正在调用 terminated()
5)TERMINATED
- 线程池彻底结束
三、最核心:ctl 的位运算机制(线程池状态是怎么存的?)
ThreadPoolExecutor 非常巧妙地使用了一个 int ctl 保存两类信息:
- 高 3 位:线程池状态(RUNNING / SHUTDOWN / ...)
- 低 29 位:当前 worker 数量
一个 ctl = 线程池状态 + 工作线程数
ctl 的结构(极其关键)
java
3 bit 29 bit
┌─────┬──────────────────────┐
│状态 │ workerCount │
└─────┴──────────────────────┘
源码中:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl 为什么要设计成 1 个 int?
因为:
- 状态和线程数经常一起判断
- 原子操作必须一次 CAS,同时判断两个值
- 合并到一个 int 可以极大提高效率
例如:
线程池在 execute(task) 时必须检查:
- 线程池状态是否 RUNNING?
- workerCount 是否小于 corePoolSize?
这两个判断可以用一次 ctl CAS 完成。
状态的取值(高 3 bit)
源码:
java
private static final int COUNT_BITS = 29;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
直接背下来:
- RUNNING 的高 3 位是
111 - SHUTDOWN 是
000 - STOP 是
001 - TIDYING 是
010 - TERMINATED 是
011
如何从 ctl 读状态?
runStateOf(ctl.get())
如何读线程数?
workerCountOf(ctl.get())
四、worker 是怎么管理线程的?
ThreadPoolExecutor 内部有个 Worker 类:
java
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable
Worker 本质上是:
- 一个线程 + AQS(可重入锁)
- 线程由 ThreadFactory 创建
- AQS 用来控制线程中断行为
Worker 生命周期:
- 创建 Worker(携带第一个任务)
- 执行第一个任务
- 从队列中不断获取任务
- 空闲超过 keepAliveTime → 线程退出
Worker 退出后 workerCount 减 1。
五、线程池在 shutdown() 时内部做了什么?
调用:
executor.shutdown();
线程池会:
- 将状态从 RUNNING 改为 SHUTDOWN
- 不再接收新任务
- 继续执行队列任务
- workerCount 逐渐变为 0
- 最终进入 TIDYING → TERMINATED
重要 :shutdown 不会中断正在执行的线程
六、shutdownNow() 做了什么?
-
将状态从 RUNNING 改为 STOP
-
中断所有正在执行任务的线程
-
清空任务队列,并返回未执行任务列表
-
workerCount 减到 0
-
进入 TERMINATED
区别总结:
| 方法 | 状态 | 是否中断任务 | 是否执行队列任务 |
|---|---|---|---|
| shutdown() | SHUTDOWN | ❌ | ✔ |
| shutdownNow() | STOP | ✔ | ❌ |
七、线程池完整运行流程图(非常关键)
java
提交任务 execute()
│
▼
┌──────────────────────────┐
│ 1. workerCount < core? │
└───────────────┬──────────┘
是│否
▼
创建核心线程执行
│
▼
队列是否可放下任务?
│ │
是 否
▼ ▼
任务入队,等待线程执行 workerCount < maximum?
│ │
是 否
▼ ▼
创建临时线程执行 执行拒绝策略
八、总结(背下来就是线程池高手)
- ThreadPoolExecutor 使用 ctl(高 3 位状态 + 低 29 位线程数) 管理线程池
- 线程池有五种状态:RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED
- execute() 的任务处理流程是三步:
- corePoolSize 能否创建线程?
- 队列能否接收任务?
- maximumPoolSize 能否创建线程?
- shutdown() 和 shutdownNow() 的根本区别:是否中断 worker、是否执行队列
- Worker 是线程池的运行单元,内部用 AQS 控制线程终止逻辑
下一篇: