最近在看 Java 线程池的实现,发现里面有一个 int 类型的成员变量,同时表示线程池运行状态和线程数量。理解了一下这块的实现,挺有意思的,所以单独拿出来跟大家分享一下。

为什么要研究一个 int 变量

其实一开始,我是在看 execute 方法的实现……

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. // 这里 c 用来获取线程数量
  6. if (workerCountOf(c) < corePoolSize) {
  7. if (addWorker(command, true))
  8. return;
  9. c = ctl.get();
  10. }
  11. // 这里 c 用来判断线程池是否处于运行状态
  12. if (isRunning(c) && workQueue.offer(command)) {
  13. int recheck = ctl.get();
  14. if (!isRunning(recheck) && remove(command))
  15. reject(command);
  16. else if (workerCountOf(recheck) == 0)
  17. addWorker(null, false);
  18. }
  19. else if (!addWorker(command, false))
  20. reject(command);
  21. }

我注意到从 ctl(AtomicInteger类型) 中取出来的这个 int 变量 c,它一会儿用来获取线程数量,一会儿又用来判断线程池是否处于运行状态。我很好奇,于是点进去看了这两个方法的实现。

  1. private static int workerCountOf(int c) {
  2. return c & CAPACITY;
  3. }
  4. private static boolean isRunning(int c) {
  5. return c < SHUTDOWN;
  6. }

这下就更疑惑了。CAPACITY 是什么?是怎么通过按位与运算得到线程数量的呢?SHUTDOWN 又是什么?有点意思,我决定好好研究一下它是怎么实现的。

怎么实现的

来看一下 ctl 这个成员变量以及相关的值的声明。

  1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  2. private static final int COUNT_BITS = Integer.SIZE - 3; // 29
  3. private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 00011111 ... ... 11111111
  4. // 状态在高位存储
  5. private static final int RUNNING = -1 << COUNT_BITS; // 11100000 ... ... 00000000
  6. private static final int SHUTDOWN = 0 << COUNT_BITS; // 00000000 ... ... 00000000
  7. private static final int STOP = 1 << COUNT_BITS; // 00100000 ... ... 00000000
  8. private static final int TIDYING = 2 << COUNT_BITS; // 01000000 ... ... 00000000
  9. private static final int TERMINATED = 3 << COUNT_BITS; // 01100000 ... ... 00000000
  10. private static int ctlOf(int rs, int wc) { return rs | wc; }

ctl 是一个 AtomicInteger 的类,就是让保存的 int 变量的更新都是原子操作,保证线程安全。 ctlOf 方法就是组合运行状态和工作线程数量。可以看到,ctlOf 方法是通过按位或的方式来实现的。为什么能这样做呢?因为,这里把一个 int 变量拆成两部分来用。前面3位用来表示状态,后面29位用来表示工程线程数量。所以,工作线程数量最大不能超过 2^29-1 ,ThreadPoolExecutor 的设计者也是考虑不太可能超过这个数,暂时就用了29位。
image-20210311222353465.png
ctl 变量的位分布

了解了 ctl 变量的结构,再回过头来看前面提到的两个方法。

  1. private static int workerCountOf(int c) {
  2. return c & CAPACITY;
  3. }
  4. private static boolean isRunning(int c) {
  5. return c < SHUTDOWN;
  6. }

workerCountOf 方法很好理解,CAPACITY 的值是00011111 … … 11111111,按位与之后去掉了前面三位,保留了后面的。所以,拿到的就是工作线程的数量。

isRunning 方法中,直接拿 ctl 的值和 SHUTDOWN 作比较。这个要先知道在 RUNNING 状态下,ctl 的值是什么样的。初始状态,ctl 的值是11100000 … … 00000000,表示 RUNNING 状态,和0个工作线程。后面,每创建一个新线程,都把 ctl 加一。当有5个工作线程时,ctl 的值是11100000 … … 00000101。在 RUNNING 状态下,ctl 始终是负值,而 SHUTDOWN 是0,所以可以通过直接比较 ctl 的值来确定状态。

再来看根据 ctl 获取状态的方法。

  1. private static int runStateOf(int c){
  2. return c & ~CAPACITY;
  3. }

~CAPACITY 按位取反后其值为11100000... ...00000000 。然后通过 & 运算后,就保留前三位的值,舍去后面的,得到其状态。

思考

善用位运算,有时候可以给我们节省很多空间。但是,在这里,明显不是为了省空间了,因为就算用两个值分开表示状态和工作线程数量,也就8个字节而已。我猜测是为了在多线程环境下保证运行状态和线程数量的统一。把这两个值放到一个 int 变量中,然后用 AtomicInteger 进行存储和读写,就可以保证这两个值始终是统一的。如果用两个变量保存,即使用了 AtomicInteger ,也可能出现一个改了,另一个还没改的情况。

参考文章

ThreadPoolExecutor 中的 ctl 变量