ctl是32位整数,高3位(bit31–bit29)存储线程池状态,低29位(bit28–bit0)存储活跃线程数;通过CAPACITY掩码和位运算分离状态与数量。

ctl 的位布局到底长什么样
ctl 是一个 AtomicInteger,本质是 32 位整数。高 3 位(bit31–bit29)存线程池状态,低 29 位(bit28–bit0)存当前活跃线程数(workerCount)。不是“拆成两个变量”,而是用一个整数、靠位掩码和位移操作实时分离出两个含义。
关键常量定义在源码里:
private static final int COUNT_BITS = 29; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 0x1FFFFFFF,即 29 个 1 private static final int RUNNING = -1 << COUNT_BITS; // 0xE0000000
比如 ctl.get() == 0xE0000001,二进制是 11100000 00000000 00000000 00000001:前三位 111 → RUNNING,后 29 位 000...001 → 当前有 1 个活跃线程。
怎么从 ctl 值里分别取出状态和线程数
源码里两个核心静态方法就是干这个的:
-
runStateOf(c):用c & ~CAPACITY清掉低 29 位,只留高 3 位状态码 -
workerCountOf(c):用c & CAPACITY清掉高 3 位,只留低 29 位线程数
例如:ctl = 0x00000005(十进制 5)
→ runStateOf(5) = 5 & ~0x1FFFFFFF = 0 → SHUTDOWN
→ workerCountOf(5) = 5 & 0x1FFFFFFF = 5 → 正在运行 5 个线程
注意:~CAPACITY 在 Java 中是带符号取反,结果是负数(如 ~0x1FFFFFFF == 0xE0000000),所以 RUNNING 状态值本身就是负数(-536870912),这是故意设计的,方便用 c 判断是否仍在运行。
为什么用位运算而不是两个独立变量
三个硬性原因:
- 保证原子性:
ctl是单个AtomicInteger,一次compareAndSet就能同时更新状态和线程数,避免竞态——比如线程数加 1 的同时状态刚好从RUNNING变为SHUTDOWN,分开更新就可能漏判 - 节省内存与缓存行:两个字段要占两处内存位置,而一个 int 更紧凑,对 CPU 缓存友好
- 状态流转约束天然成立:高 3 位只能是 0–3 或 -1,不可能出现 “状态=STOP 且线程数=2000000000” 这种逻辑矛盾值,因为位域互不干扰
这不是炫技,是并发场景下对一致性与性能的直接妥协。你改用两个 volatile 字段,就得加锁或复杂 CAS 循环,吞吐立刻掉一截。
调试时怎么看 ctl 的真实值
别只看十进制输出,它对理解位布局毫无帮助。打印时务必转二进制或十六进制:
-
Integer.toBinaryString(ctl.get())(补全 32 位需自己 pad) -
String.format("0x%08X", ctl.get())最实用
常见陷阱:
- 误把
ctl == 0当作空闲线程池——其实是SHUTDOWN状态且线程数为 0,但队列可能还有任务 - 用
ctl > 0判断是否运行中——错,RUNNING是负数;必须用isRunning(ctl.get())即c < SHUTDOWN - 手动构造 ctl 值时位移算错,比如写成
1 << 30而不是1 << COUNT_BITS,导致状态位溢出到符号位,行为不可预测
真正难的不是看懂位运算公式,而是意识到:所有状态检查(execute、addWorker、processWorkerExit)都建立在这个单一整数的解读之上。一旦对 runStateOf 或 workerCountOf 的语义理解偏差一点,整个线程池的行为就会偏移——而这种 bug 往往只在高并发压测时才暴露。

















