二进制操作在框架设计与底层实现中非常常见,利用二进制在不同场景下往往能够带来计算速度更快、内存占用更少等多方面优势;
下面,我们结合线程池的状态管理机制,详细看一看如何通过操作二进制来管理状态,整个分析过程也能让我们感受到编程之美~
线程池状态
首先,为了让本文内容更加完整,我们先了解一下线程池的几种核心状态,整理如下: 
线程池状态一共分为5种:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED
状态代表的含义
- RUNNING:(运行中)接收新的task,并且处理正在队列中等待的task,不会中断正在执行的任务
- SHUTDOWN:(关闭中)不再接收新的task,只处理已经进入队列的task,不会中断正在执行的任务
- STOP:(停止)不接收新的task,也不再处理队列中等待的task,并且会中断正在执行中的任务
- TIDYING:(整理中)所有task都已经终止,此时上述提到的workCount,也就是当前活跃线程数为0,随后会执行terminated()钩子方法
- TERMINATED:(已终止)表示上述TIDYING阶段已经结束,说明当前线程池已经被完全关闭
状态转换
大致流程如下:
RUNNING --> SHUTDOWN --> STOP --> TIDYING --> TERMINATED
上述流程是单向顺序转换,也就是说不会出现类似STOP --> SHUTDOWN 这样的逆向变化;
另外,并不是每一种状态都一定会经历;
那么,线程池会在什么时候发生状态转换呢?
- RUNNING -> SHUTDOWN:调用关闭线程池的方法
shutdown()之后 - RUNNING or SHUTDOWN -> STOP:调用
shutdownNow()方法后,无论当前线程池处于RUNNING还是SHUTDOWN,都会直接切换到STOP状态 - SHUTDOWN -> TIDYING:在SHUTDOWN状态下,当等待队列和正在执行的任务都为空时,状态切换为TIDYING
- STOP -> TIDYING:在STOP状态下,当正在执行的任务全部中断完成后,状态切换为TIDYING
- TIDYING -> TERMINATED:在TIDYING状态下,当所有terminated()钩子方法执行完成后,状态切换为TERMINATED,表示线程池关闭结束!
管理线程池状态
在线程池中,线程池状态和线程池当前活跃线程数,是通过一个AtomicInteger变量统一管理的
什么? 一个变量同时管理两个看起来并不相关的状态? 没错;
CTL变量何许人也
我们先来看一下线程池在这部分的源码实现:
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; }
private static boolean isRunning(int c) { return c < SHUTDOWN;}下面,我们来深入拆解一下上述实现: 线程池一共包含5种状态,如下所示;关于每种状态的具体含义以及状态之间的转换规则,前文已经介绍过:
private static final int COUNT_BITS = Integer.SIZE - 3;
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;我们知道,在ja va中,int类型占用4个字节,也就是32位存储空间。上述几种状态在底层对应的二进制值分别为:
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)
左移<COUNT_BITS = Integer.SIZE - 3,也就是 COUNT_BITS = 29。这句话的含义是:用32位中的前3位存储线程池状态,后29位存储线程池中当前工作线程的数量。经过<< COUNT_BITS后,就会变成下面的二进制:
1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000 0100 0000 0000 0000 0000 0000 0000 0000 0110 0000 0000 0000 0000 0000 0000 0000
我们可以清楚地看到,前三位存储的就是用于标识线程池状态的二进制值
对于初始化这些状态的变量AtomicInteger ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0))这里初始化了AtomicInteger变量ctl,其中ctlOf(RUNNING, 0) 的代码如下:
private static int ctlOf(int rs, int wc) { return rs | wc; }其中rs表示线程池当前状态,wc为work count,表示当前工作线程的数量
这里传入的是ctlOf(RUNNING, 0),当前状态为RUNNING,也就是1110 0000 0000 0000 0000 0000 0000 0000;wc为0,也就是当前工作线程数为0,其二进制为0000 0000 0000 0000 0000 0000 0000 0000。两者进行"|"按位或操作,即
1110 0000 0000 0000 0000 0000 0000 0000 | 0000 0000 0000 0000 0000 0000 0000 0000 = 1110 0000 0000 0000 0000 0000 0000 0000
上述得到的结果1110 0000 0000 0000 0000 0000 0000 0000就表示:当前线程池状态为RUNNING,线程池活跃线程数为0!
如何管理?
通过上面创建的ctl变量,我们就可以同时获取 线程池当前状态 和 线程池中活跃线程个数 这两个信息:
先看如何获取线程池当前状态。既然现在已经知道ctl的前3位表示线程池状态,那么直接构造一个前3位为1、后29位为0的int值,再进行按位与操作即可。源码中的实现也正是如此:使用方法runStateOf
private static int runStateOf(int c) { return c & ~CAPACITY; }其中CAPACITY = (1 << COUNT_BITS) - 1,转换成二进制为: 0001 1111 1111 1111 1111 1111 1111 1111。取反"~"之后,二进制为: 1110 0000 0000 0000 0000 0000 0000 0000,也就是将前3位全部变为1,后29位全部变为0; 接下来,传入的ctl变量与~CAPACITY做“&”操作,就只会保留ctl变量的前3位,后29位全部清零;
例如:一个表示当前状态为STOP、当前活跃线程数为3的ctl变量为: 0010 0000 0000 0000 0000 0000 0000 0011,和上面得到的1110 0000 0000 0000 0000 0000 0000 0000做“&”操作后得到: 0010 0000 0000 0000 0000 0000 0000 0000。这与前面分析出的STOP状态二进制完全一致! 也就意味着成功获得了当前线程池的状态!
获取线程池当前活跃线程数也很简单。因为ctl变量32位中的后29位存储的是当前活跃线程数,所以直接构造一个前3位为0、后29位为1的int值,然后进行按位与即可,这就是方法workerCountOf
private static int workerCountOf(int c) { return c & CAPACITY; }前面已经知道,CAPACITY为:0001 1111 1111 1111 1111 1111 1111 1111
比如,一个表示当前状态为STOP、当前活跃线程数为3的ctl变量是:0010 0000 0000 0000 0000 0000 0000 0011,与 0001 1111 1111 1111 1111 1111 1111 1111 进行按位与操作后得到:0000 0000 0000 0000 0000 0000 0000 0011,这个结果就表示当前线程池中的活跃线程数量为3!
一些方法
1、计算ctl的值
方法:
private static int ctlOf(int rs, int wc) { return rs | wc; }其中,入参rs表示当前线程池状态,wc表示当前活跃线程数,二者通过“|”按位或进行合并即可。上述代码能够正确工作的前提是:rs只使用前3位,wc只使用后29位!
2、判断当前线程池是否正在运行
方法:
private static boolean isRunning(int c) { return c <小于SHUTDOWN;}值即可!上面我们已经知道,5种状态里只有RUNNING小于0,SHUTDOWN等于0,其他状态都大于0,因此只要给定的ctl值小于SHUTDOWN,就说明线程池当前仍然处于运行状态!
最后
以上,我们介绍了线程池的状态管理部分。其核心思想就是通过一个整型变量中不同位置的二进制位,分别标识线程池状态和工作线程数量。这种设计既巧妙又高效,在学习和工作中还会遇到更多类似的优秀实现,等待着我们作为程序员去不断发现与理解;
