Java 线程池 ThreadPoolExecutor 的状态控制变量 ctrl

如下是源代码。

线程池的主要控制状态 ctl 是一个原子整数,它打包了两个概念字段:

  • workerCount:表示当前有效运行的线程数。
  • runState:表示线程池的状态(如是否正在运行、关闭等)。

为了将这两个字段打包成一个 int,我们将 workerCount 限制为 (2^{29} - 1)(约5亿),而不是 (2^{31} - 1)(约20亿)。如果将来这成为一个问题,可以将变量改为 AtomicLong,并调整下面的移位/掩码常量。但在需要出现之前,使用 int 可以使代码更快且更简单。

workerCount 表示被允许启动且未被允许停止的工作线程数量。这个值可能暂时与实际存活线程的数量不同,例如当 ThreadFactory 在被要求创建线程时失败,或者退出线程在终止前仍在进行清理工作时。用户可见的线程池大小报告为 workers 集合的当前大小。

runState 提供主要的生命周期控制,取以下值:

  • RUNNING:接受新任务并处理队列中的任务。
  • SHUTDOWN:不接受新任务,但处理队列中的任务。
  • STOP:不接受新任务,不处理队列中的任务,并中断正在进行的任务。
  • TIDYING:所有任务已终止,workerCount 为零,正在转换到 TIDYING 状态的线程将运行 terminated() 钩子方法。
  • TERMINATEDterminated() 方法已完成。

这些值之间的数值顺序很重要,以便进行有序比较。runState 随时间单调增加,但不一定经过每个状态。状态转换如下:

  • RUNNING -> SHUTDOWN:调用 shutdown() 时。
  • RUNNING 或 SHUTDOWN -> STOP:调用 shutdownNow() 时。
  • SHUTDOWN -> TIDYING:当队列和线程池都为空时。
  • STOP -> TIDYING:当线程池为空时。
  • TIDYING -> TERMINATED:当 terminated() 钩子方法完成后,等待在 awaitTermination() 中的线程将在状态达到 TERMINATED 时返回。

检测从 SHUTDOWNTIDYING 的转换不如预期那样直接,因为队列可能在 SHUTDOWN 状态期间变为空或再次变为非空。我们只能在看到队列为空后,再确认 workerCount 为 0 才能终止(有时需要重新检查)。


关键点总结:

  • ctl:一个原子整数,包含 workerCountrunState
  • workerCount:表示有效线程数,最大为 (2^{29} - 1)。
  • runState:表示线程池的状态,有多个状态值。
  • 状态转换:根据调用的方法和条件,线程池状态会从一个状态转换到另一个状态。
  • 终止检测:检测从 SHUTDOWNTIDYING 的转换较为复杂,需要确保队列为空且 workerCount 为 0。

以下是代码的逐行解析,按 变量方法 分类整理为表格:


变量定义表格

变量名 值/表达式 作用
ctl AtomicInteger(ctlOf(RUNNING, 0)) 原子整数,高3位存储线程池状态(runState),低29位存储工作线程数(workerCount)。初始状态为RUNNING,线程数为0。
COUNT_BITS Integer.SIZE - 3 工作线程数占用的位数(29位),因为32位整数的高3位用于状态。
CAPACITY (1 << COUNT_BITS) - 1 工作线程数的最大值掩码(低29位全1,例如 00011111...),用于从ctl中提取workerCount
RUNNING -1 << COUNT_BITS 运行状态:线程池接受新任务并处理队列中的任务。对应二进制高3位为111
SHUTDOWN 0 << COUNT_BITS 关闭状态:不再接受新任务,但处理队列中的剩余任务。高3位为000
STOP 1 << COUNT_BITS 停止状态:不再接受新任务,不处理队列任务,并中断进行中的任务。高3位为001
TIDYING 2 << COUNT_BITS 整理状态:所有任务已终止,线程数归零,将执行terminated()钩子方法。高3位为010
TERMINATED 3 << COUNT_BITS 终止状态:terminated()方法已完成。高3位为011

方法解析表格

方法名 输入参数 返回值/逻辑 作用
runStateOf(int c) c(ctl的当前值) return c & ~CAPACITY; ctl中提取线程池状态(高3位),通过掩码~CAPACITY(高3位为1,低29位为0)过滤。
workerCountOf(int c) c(ctl的当前值) return c & CAPACITY; ctl中提取工作线程数(低29位),通过掩码CAPACITY(低29位为1)过滤。
ctlOf(int rs, int wc) rs(状态)、wc(线程数) return rs | wc; 将线程池状态(高3位)和工作线程数(低29位)合并为一个整数,形成完整的ctl值。
runStateLessThan(int c, int s) c(ctl值)、s(目标状态) return c < s; 判断当前状态是否早于目标状态(例如,RUNNING是否在SHUTDOWN之前)。状态值越小,阶段越早。
runStateAtLeast(int c, int s) c(ctl值)、s(目标状态) return c >= s; 判断当前状态是否等于或晚于目标状态(例如,STOP是否在SHUTDOWN之后)。状态值越大,阶段越晚。
isRunning(int c) c(ctl值) return c < SHUTDOWN; 判断线程池是否处于运行状态(RUNNING)。因为SHUTDOWN的高3位是000,而其他状态的高3位更大,所以c < SHUTDOWN等价于状态为RUNNING

关键设计思想

  1. 状态与线程数的合并存储
    用一个AtomicIntegerctl)同时保存线程池状态和工作线程数,通过位操作分离高3位和低29位,减少锁竞争并提高性能

  2. 状态顺序与数值关系
    状态值按生命周期顺序定义(RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED),通过直接比较数值大小即可判断状态阶段,避免复杂的逻辑判断

  3. 掩码操作

    • CAPACITY掩码用于提取低29位的线程数(workerCount)。
    • ~CAPACITY掩码用于提取高3位的状态(runState)。
  4. 原子性保证
    ctl使用AtomicInteger确保对状态和线程数的修改是原子的,避免多线程环境下的竞态条件。


示例说明

假设ctl的值为 11100000...00000010(高3位为111表示RUNNING,低29位为2):

  • runStateOf(ctl.get())RUNNING
  • workerCountOf(ctl.get())2
  • isRunning(ctl.get())true(因为RUNNING < SHUTDOWN

通过这种设计,线程池的高效状态管理得以实现。

posted @ 2025-03-08 22:33  皮皮是个不挑食的好孩子  阅读(37)  评论(0)    收藏  举报