最近看到有些博客提到线程池需要掌握的问题清单,发现自己很多地方是是一知半解的状态,正好借此机会,带着问题去回顾了一波 Java 线程池的源码。
ctl 为何物?
线程池的运作过程对状态的检查非常严格,几乎是走两步一个检查,检查线程池的状态,有效线程的数目,而它们都是基于一个整型变量来实现的,它的「身子」也许很单薄,但是肩上的责任重大。
// 本质是 Integer 型变量,进行了原子性的封装
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
一饰两角
大家可能会想,一个变量怎么能够同时表示两个状态,我当时也非常疑惑,这里就要提到一个巧妙的设计——高低位表示法(名字是我瞎掰的)。
紧随在 ctl 变量后面被初始化的两个变量
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
COUNT_BITS 的值为 29(整型 32 位);
CAPACITY = 1 << 29 表示将 1 往左移动了 29 位,换算之后就是 1 * 2 的 29 次方,高 3 位被空出来了,而低 29 位用来表示 CAPACITY 即线程池的最大线程容量
1
0000 0000 0000 0001
1 << 29 - 1
0001 1111 1111 1111
高 3 位用来表示状态,因为有 5 种状态,需要 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;
状态通过数字大小区分,最小的是 RUNNING 状态,最大的是 TERMINATED 状态,一个预示着开始,一个代表结束,逐渐递增,会经过很多中间状态,状态间的转换后面的文章会详细说明。
通过 ctl 怎么取得具体状态呢?我们说的高 3 位表示法又是怎么实现的呢?这就要依赖于之前计算好的 CAPACITY 变量。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 0001 1111 1111 1111 二进制表示
来看看线程池获取状态的方法代码,参数 c 是 ctl 变量当前的值。
private static int runStateOf(int c) { return c & ~CAPACITY; }
首先对 CAPACITY 进行「取反」操作。
0001 1111 1111 1111
~ 操作符取反,可以看到低 29 位都变成了 0
1110 0000 0000 0000
ctl 和取反后的 CAPACITY 进行 & 运算,熟悉 & 运算的都知道,1 & 1 = 1;1 & 0 = 0,运算之后,ctl 低 29 位都会被置为 0,通过这个方法来屏蔽掉低 29 位的数值干扰。
获取有效线程数量也同理,高 3 位的数值被屏蔽掉
private static int workerCountOf(int c) { return c & CAPACITY; }
总结
经过一顿分析,我们得知线程池就是通过以上骚操作来实现 ctl 一饰两角的「表演」。
所以在阅读线程池源码的过程中,不要惊讶为什么通过一个 ctl 即能够获取线程数量,又能得知线程池状态。另外还会频繁出现类似以下条件表达式,rs 表示的是当前线程状态。
rs >= SHUTDOWN
rs >= STOP
因为从 RUNNING 到 TERMINATED 状态间的取值是单调递增的,所以可以通过此方法来进行条件判定。
希望看完这篇文章,能够帮助你更流畅地阅读 Java 线程池源码,干就完事了!
网友评论