最近在看 Java 线程池的实现,发现里面有一个 int 类型的成员变量,同时表示线程池运行状态和线程数量。理解了一下这块的实现,挺有意思的,所以单独拿出来跟大家分享一下。
为什么要研究一个 int 变量
其实一开始,我是在看 execute 方法的实现……
public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();// 这里 c 用来获取线程数量if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 这里 c 用来判断线程池是否处于运行状态if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (!isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}
我注意到从 ctl(AtomicInteger类型) 中取出来的这个 int 变量 c,它一会儿用来获取线程数量,一会儿又用来判断线程池是否处于运行状态。我很好奇,于是点进去看了这两个方法的实现。
private static int workerCountOf(int c) {return c & CAPACITY;}private static boolean isRunning(int c) {return c < SHUTDOWN;}
这下就更疑惑了。CAPACITY 是什么?是怎么通过按位与运算得到线程数量的呢?SHUTDOWN 又是什么?有点意思,我决定好好研究一下它是怎么实现的。
怎么实现的
来看一下 ctl 这个成员变量以及相关的值的声明。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3; // 29private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 00011111 ... ... 11111111// 状态在高位存储private static final int RUNNING = -1 << COUNT_BITS; // 11100000 ... ... 00000000private static final int SHUTDOWN = 0 << COUNT_BITS; // 00000000 ... ... 00000000private static final int STOP = 1 << COUNT_BITS; // 00100000 ... ... 00000000private static final int TIDYING = 2 << COUNT_BITS; // 01000000 ... ... 00000000private static final int TERMINATED = 3 << COUNT_BITS; // 01100000 ... ... 00000000private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl 是一个 AtomicInteger 的类,就是让保存的 int 变量的更新都是原子操作,保证线程安全。 ctlOf 方法就是组合运行状态和工作线程数量。可以看到,ctlOf 方法是通过按位或的方式来实现的。为什么能这样做呢?因为,这里把一个 int 变量拆成两部分来用。前面3位用来表示状态,后面29位用来表示工程线程数量。所以,工作线程数量最大不能超过 2^29-1 ,ThreadPoolExecutor 的设计者也是考虑不太可能超过这个数,暂时就用了29位。
ctl 变量的位分布
了解了 ctl 变量的结构,再回过头来看前面提到的两个方法。
private static int workerCountOf(int c) {return c & CAPACITY;}private static boolean isRunning(int c) {return c < SHUTDOWN;}
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 获取状态的方法。
private static int runStateOf(int c){return c & ~CAPACITY;}
~CAPACITY 按位取反后其值为11100000... ...00000000 。然后通过 & 运算后,就保留前三位的值,舍去后面的,得到其状态。
思考
善用位运算,有时候可以给我们节省很多空间。但是,在这里,明显不是为了省空间了,因为就算用两个值分开表示状态和工作线程数量,也就8个字节而已。我猜测是为了在多线程环境下保证运行状态和线程数量的统一。把这两个值放到一个 int 变量中,然后用 AtomicInteger 进行存储和读写,就可以保证这两个值始终是统一的。如果用两个变量保存,即使用了 AtomicInteger ,也可能出现一个改了,另一个还没改的情况。
