详解Java线程池的ctl(线程池控制状态)【源码分析】
0.综述
1.  ctl 是线程池源码中常常⽤到的⼀个变量。
2. 它的主要作⽤是记录线程池的⽣命周期状态和当前⼯作的线程数。
3. 作者通过巧妙的设计,将⼀个整型变量按⼆进制位分成两部分,分别表⽰两个信息。
1.声明与初始化
  源码:
1private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  分析⼀波:
1.  ctl (线程池控制状态)是原⼦整型的,这意味这对它进⾏的操作具有原⼦性。
2. 如此⼀来,作为 ctl 组成部分的 runState (线程池⽣命周期状态)和 workerCount (⼯作线程数) 也将同时具有原⼦性。
3.  ThreadPoolExecutor 使⽤  ctlOf ⽅法来将  runState 和  workerCount 两个变量(都是整型)打包成⼀个 ctl  变量。稍后将解读这个⽅法的
实现。
2.两个⼯具⼈常量 COUNT_BITS 和 CAPACITY
  源码:
1private static final int COUNT_BITS = Integer.SIZE - 3;
2private static final int CAPACITY  = (1 << COUNT_BITS) - 1;
  分析⼀波:
1. COUNT_BITS  常量的值为 Integer.SIZE - 3 ,其中 Integer.SIZE 为整型最⼤位数,在本⽂剩余部分,我们取其为 32 。
2. 如此 COUNT_BITS 实际的值其实就是 29 。这⾥有些读者可能会有 “为什么减去的数是 3 ⽽不是别的” 的疑惑,这将在后⽂得到解答。
3. CAPACITY  常量的值为  (1 << COUNT_BITS) - 1 ,其中 << 为左移运算符,这么说可能不太直观,我以⼆进制直接写出这个数将有助
于理解:
1
0000 0000 0000 0001
1 << 29 - 1
0001 1111 1111 1111
4. 因此在接下来的代码中, COUNT_BITS 就⽤来表⽰分隔runState 和workerCount 的位数;
5. ⽽CAPACITY 则作为取这两个变量( runState 和 workerCount )的⼯具(具体是怎么使⽤的请看下⽂)
3.线程池⽣命周期状态常量
  源码:
1private static final int RUNNING    = -1 << COUNT_BITS;
2private static final int SHUTDOWN  =  0 << COUNT_BITS;
3private static final int STOP      =  1 << COUNT_BITS;
4private static final int TIDYING    =  2 << COUNT_BITS;
5private static final int TERMINATED =  3 << COUNT_BITS;
  分析⼀波:
1. 这⾥解答了上边关于 COUNT_BITS 变量为什么要减 3 的问题:因为线程池的⽣命周期有 5 个状态,为了表达这 5 个状态,我们需要
3 个⼆进制位。
2. 对线程池的⽣命周期有兴趣的读者请百度线程池⽣命周期;不明⽩为什么 5 个状态需要 3 个⼆进制位的请百度⼆进制。
3. 注意到这⾥标注状态使⽤的并不是 -1 ~ 3 ,⽽是这 5 个数字分别左移 COUNT_BITS 位,这样做的好
处将在接下来的代码中得到体
现。
4.打包函数与拆包函数
  源码:
1//拆包函数
2private static int runStateOf(int c)    { return c & ~CAPACITY; }
3private static int workerCountOf(int c)  { return c & CAPACITY; }
4//打包函数
5private static int ctlOf(int rs, int wc) { return rs | wc; }
  分析⼀波:
1. 此处我们解答了 CAPACITY 常量的作⽤,之前提到过,他是⼀个后 29 位均为 1 ,前 3 位为 0 的整数,因此我们可以通过:
2. 对 CAPACITY 和 ctl 进⾏ & (按位与)操作就能取到 ctl 的后 29 位,即  workerCount 。
3. 对 CAPACITY 进⾏ ~ (按位取反)操作后,再和 ctl 进⾏ & 操作就能取到 runState 。它的⾼ 3 位是 ctl 的⾼ 3 位,低 29 位为 0。这也解
释了为什么之前提到的⽣命周期常量要在 -1 ~ 3 的基础上再左移 29 位,因为不在常量初始化处左移的话就要在拆包的时候右移来保证取到的是正确的数值。然⽽拆包操作是要经常进⾏的,⽽常量的初始化只有⼀次。两下对⽐,明显在初始化时左移是效率更⾼的选择。
4. 除了拆包时的效率,常量初始化时左移也提⾼了打包函数的效率:此处打包函数可以直接对 runState 和 workerCount 进⾏ | (按位或)
操作来得到 ctl 变量,就是因为 runState 的⾼ 3 位为有效信息,⽽ workerCount 的低 29 位为有效信息,合起来正好得到⼀个含 32 位有效信息的整型变量。
5. 说到这⾥可能仍有些让⼈疑惑,我将再以⼆进制的形式表⽰出所有涉及到的变量/常量:
//下⽂中a和b分别代表runState和workerCount的有效信息
//CAPACITY
0001 1111 1111 1111
//ctl
aaab bbbb bbbb bbbb
//runState
aaa0 0000 0000 0000
//workerCount
000b bbbb bbbb bbbb
5.运⾏状态的判断
  源码:
1private static boolean runStateLessThan(int c, int s) {
2return c < s;
3    }
4
5private static boolean runStateAtLeast(int c, int s) {
6return c >= s;
7    }
8
9private static boolean isRunning(int c) {
10return c < SHUTDOWN;
11    }
  分析⼀波:
1. 注意这⾥传⼊的s是⽤了之前定义的⽣命周期常量。
2. 这⾥判断状态的⼤⼩时,直接将c 和s 进⾏了⽐较,这是因为代表状态的信息占据了两个变量的⾼ 3 位,⽽⽐较⾼位的⼤⼩时,低位是
没有影响的。
6.修改ctl中workCount的⼤⼩
  源码:
1private boolean compareAndIncrementWorkerCount(int expect) {
2return ctlpareAndSet(expect, expect + 1);
3    }
4
5private boolean compareAndDecrementWorkerCount(int expect) {
6return ctlpareAndSet(expect, expect - 1);
7    }
8
9private void decrementWorkerCount() {
10do {} while (! ()));
11    }
  分析⼀波:
1. 注意到这⾥的修改都使⽤了原⼦整型的CAS⽅法。
字符串常量池和运行时常量池的联系7.修改ctl中runState的⼤⼩
  源码:
1 ctlpareAndSet(c, ctlOf(targetState, workerCountOf(c)))
  分析⼀波:
1. 注意到修改 runState 并没有再提供专门的⽅法,⽽是直接使⽤了原⼦整型的CAS⽅法来替换原来的 ctl 。
8.仍存在的疑问
Q1:如果经过递增 compareAndIncrementWorkerCount ,使得 workerCount 的⼤⼩超过29位,会发⽣什么?会有安全检查吗?
A1:有安全检查,在ThreadPoolExecutor类的addWorker⽅法中有这样⼀⾏代码:
1if (wc >= CAPACITY ||
2    wc >= (core ? corePoolSize : maximumPoolSize))
3return false;
Q2:为什么为 workerCount 的修改提供了⽅法,却没有为 runState 的修改提供?

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。