1. 前言
- ctl 是线程池源码中常常用到的一个变量。
- 它的主要作用是记录线程池的生命周期状态和当前工作的线程数。
- 作者通过巧妙的设计,将一个整型变量按二进制位分成两部分,分别表示两个信息。
2. 源码解析
源码部分
/**
* The main pool control state, ctl, is an atomic integer packing
* two conceptual fields
* workerCount, indicating the effective number of threads
* runState, indicating whether running, shutting down etc
*
* In order to pack them into one int, we limit workerCount to
* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
* billion) otherwise representable. If this is ever an issue in
* the future, the variable can be changed to be an AtomicLong,
* and the shift/mask constants below adjusted. But until the need
* arises, this code is a bit faster and simpler using an int.
*
* The workerCount is the number of workers that have been
* permitted to start and not permitted to stop. The value may be
* transiently different from the actual number of live threads,
* for example when a ThreadFactory fails to create a thread when
* asked, and when exiting threads are still performing
* bookkeeping before terminating. The user-visible pool size is
* reported as the current size of the workers set.
*
* The runState provides the main lifecycle control, taking on values:
*
* RUNNING: Accept new tasks and process queued tasks
* SHUTDOWN: Don't accept new tasks, but process queued tasks
* STOP: Don't accept new tasks, don't process queued tasks,
* and interrupt in-progress tasks
* TIDYING: All tasks have terminated, workerCount is zero,
* the thread transitioning to state TIDYING
* will run the terminated() hook method
* TERMINATED: terminated() has completed
*
* The numerical order among these values matters, to allow
* ordered comparisons. The runState monotonically increases over
* time, but need not hit each state. The transitions are:
*
* RUNNING -> SHUTDOWN
* On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
* On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
* When both queue and pool are empty
* STOP -> TIDYING
* When pool is empty
* TIDYING -> TERMINATED
* When the terminated() hook method has completed
*
* Threads waiting in awaitTermination() will return when the
* state reaches TERMINATED.
*
* Detecting the transition from SHUTDOWN to TIDYING is less
* straightforward than you'd like because the queue may become
* empty after non-empty and vice versa during SHUTDOWN state, but
* we can only terminate if, after seeing that it is empty, we see
* that workerCount is 0 (which sometimes entails a recheck -- see
* below).
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
基础常量
线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。
线程池内部使用一个原子整型变量 ctl
维护两个值:运行状态(runState)和线程数量 (workerCount) 其中高3位保存runState,低29位保存workerCount当前池线程数。如下代码所示:
// 初始化ctl值为0,并设置状态为RUNNING
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 结果:32-3=29,就用来表示分隔runState 和workerCount 的位数
// 之所以-3,因为线程池的生命周期有 5 个状态,为了表达这 5 个状态,我们需要 3 个二进制位。
private static final int COUNT_BITS = Integer.SIZE - 3;
// 结果:000 1 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
目前我们已经有了三个变量信息(如何使用这三个值,请看下文):
- clt:保存了运行状态(runState)和线程数量 (workerCount) 的值
- COUNT_BITS:29,分隔runState 和workerCount 的位数
- CAPACITY(2进制):000 1 1111 1111 1111 1111 1111 1111 1111,前三位为0,后29位为1
可能你对【CAPACITY】二进制的值有所疑惑,那么我们来看看推演过程
1. (1 << COUNT_BITS) - 1可平替为 (1 << 29) - 1
2. 首先看 1 的二进制代表 0000 0000 0000 0000 0000 0000 0000 0001。
3. 向左移 29 位得到 0010 0000 0000 0000 0000 0000 0000 0000。
4. 数据减 1 得到 0001 1111 1111 1111 1111 1111 1111 1111。
我们都知道线程池有五个状态,它在线程池中分布是如下表示的:
- RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务。
- SHUTDOWN:指调用了 shutdown() 方法,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
- STOP:指调用了 shutdownNow() 方法,不再接受新提交的任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。
- TIDYING: 所有任务都执行完毕,workerCount 有效线程数为 0。
- TERMINATED:终止状态,当执行 terminated() 后会更新为这个状态。
源码如下
// 结果29
private static final int COUNT_BITS = Integer.SIZE - 3;
// runState is stored in the high-order bits
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;
我们来推算下他们值(2进制表示)
/**
* 各个值的二进制表示:
*
* 1111 1111 1111 1111 1111 1111 1111 1111 (-1)
* 0000 0000 0000 0000 0000 0000 0000 0000 (0)
* 0000 0000 0000 0000 0000 0000 0000 0001 (1)
* 0000 0000 0000 0000 0000 0000 0000 0010 (2)
* 0000 0000 0000 0000 0000 0000 0000 0011 (3)
*
* 【分析】:
* 初始容量值,高三位全是0,低29位全是1;后续操作会以此为基础进行操作
* CAPACITY(29) 000 1 1111 1111 1111 1111 1111 1111 1111
*
* ---------------3位-1位 -28位---
* 【前三位,表明状态位,后29位表明线程个数,即 2^29 - 1 基本够用了】
*
* RUNNING(-536870912) 111 0 0000 0000 0000 0000 0000 0000 0000
* SHUTDOWN(0) 000 0 0000 0000 0000 0000 0000 0000 0000
* STOP(536870912) 001 0 0000 0000 0000 0000 0000 0000 0000
* TIDYING(1073741824) 010 0 0000 0000 0000 0000 0000 0000 0000
* TERMINATED(1610612736) 011 0 0000 0000 0000 0000 0000 0000 0000
*
* /
根据以上分析得,我们只需要重点关注高三位:
- RUNNING:对应的高3位值是111
- SHUTDOWN:对应的高3位值是000。
- STOP: 对应的高3位值是001。
- TIDYING:对应的高3位值是010。
- TERMINATED:对应的高3位值是011。
打包函数与拆包函数
拆包函数
上面我们曾说过,线程池内部使用一个原子整型变量 ctl
维护两个值:运行状态(runState)和线程数量 (workerCount) 其中高3位保存runState,低29位保存workerCount当前池线程数
这样我们就可以知道这两个方法的目的:获取当前参数的高3位和低29位
// 获取当前运行状态
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
// 获取工作线程数
private static int workerCountOf(int c) {
return c & CAPACITY;
}
让我们复习下&(与运算): 只要有0则为0
让我们复习下|(或运算): 只要有1则为1
运行状态
c & ~CAPACITY :意为CAPACITY取反后与c进行&操作,得到当前入参二进制的前三位(运行状态)
我们以c为【RUNNING】状态举例
CAPACITY结果 000 1 1111 1111 1111 1111 1111 1111 1111
~CAPACITY取反结果 111 0 0000 0000 0000 0000 0000 0000 0000 (前三位全为1,后29位全为0)
c的结果 111 X XXXX XXXX XXXX XXXX XXXX XXXX XXXX (由上诉可知RUNNING高3位值是111)
c & ~CAPACITY的结果 111 0 0000 0000 0000 0000 0000 0000 0000 (与RUNNING对应的结果一致)
获取线程数量
我们以2个线程数来举例
CAPACITY结果 000 1 1111 1111 1111 1111 1111 1111 1111
2的二进制表示 XXX 0 0000 0000 0000 0000 0000 0000 0010
c & CAPACITY的结果 000 0 0000 0000 0000 0000 0000 0000 0010
打包函数
// rs 运行状态
// wc 工作线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; }
对 runState 和 workerCount 进行 | (按位或)操作来得到 ctl 变量
就是因为 runState 的高 3 位为有效信息,而 workerCount 的低 29 位为有效信息,合起来正好得到一个含 32 位有效信息的整型变量。
为什么之前提到的生命周期常量要在 -1 ~ 3 的基础上再左移 29 位,因为不在常量初始化处左移的话就要在拆包/打包的时候右移来保证取到的是正确的数值。然而拆包/打包操作是要经常进行的,而常量的初始化只有一次。两下对比,明显在初始化时左移是效率更高的选择。