在学习ThreadPoolExecutor之前,需要先了解下此类源码依赖的基础知识。
一。位操作基础知识
二进制如何表示整数
Java的正数与负数的二进制表示:
Integer.toBinaryString(5)
//整数0开始,负数1开始,负数等于正数的反码再加一,左边是高位,右边是低位
正数5:0000 0000 0000 0000 0000 0000 0000 0101
反码 :1111 1111 1111 1111 1111 1111 1111 1010
加一 :1111 1111 1111 1111 1111 1111 1111 1011
负数5:1111 1111 1111 1111 1111 1111 1111 1011
负数表示相关知识:
-
原码:正数原码,前面0变1.
-
反码:最前面1不变,后面的取反。
-
补码:最前面1不变,后面的取反,末位加1.
JAVA使用补码表示,补码的好处
0000 0000 0000 0000 0000 0000 0000 0101
+ 1111 1111 1111 1111 1111 1111 1111 1011
-------------------------------------------------
0000 0000 0000 0000 0000 0000 0000 0000
常见的二进制运算
//~求反:1变0,0变1
正数5:0000 0000 0000 0000 0000 0000 0000 0101
反码 :1111 1111 1111 1111 1111 1111 1111 1010
//&求与,相乘,两个都为1才为1,其他情况均为0
5表示成:0000 0000 0000 0000 0000 0000 0000 0101
6表示成:0000 0000 0000 0000 0000 0000 0000 0110
进行& :0000 0000 0000 0000 0000 0000 0000 0100
得到:4
//|求或,或,都为0才为0其它都为1
5表示成: 0000 0000 0000 0000 0000 0000 0000 0101
6表示成: 0000 0000 0000 0000 0000 0000 0000 0110
进行| : 0000 0000 0000 0000 0000 0000 0000 0111
得到:7
//^不同,不同为1,相同为0
5表示成:0000 0000 0000 0000 0000 0000 0000 0101
6表示成:0000 0000 0000 0000 0000 0000 0000 0110
进行 ^ : 0000 0000 0000 0000 0000 0000 0000 0011
得到:3
//<<乘2 左边移动,右面填充0
5表示成: 0000 0000 0000 0000 0000 0000 0000 0101
进行 <<1 操作:0000 0000 0000 0000 0000 0000 0000 1010
//>>除以2 右边移动,左边填充0
5表示成: 0000 0000 0000 0000 0000 0000 0000 0101
进行 >>>1 操作:0000 0000 0000 0000 0000 0000 0000 0010
二。位操作在ThreadPoolExecutor使用场景(一个整数拆成两个用)
整数有32位,ThreadPoolExecutor就是把32位拆成了两部分:高3位是一个整数,表示运行状态;低29位表示运行的线程数。
取哪部分的值:直接和其他位为0的求&就可以,可以看下下边加了注释的代码,结合位的知识和代码中的二进制串,应该很容易看懂,不再赘述过多,通过实例看起来更轻松。
//ctl的值倍拆分为了两部分
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//RUNNING -1状态放入高三位 11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//SHUTDOWN 0状态放入高三位 00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//STOP 1状态放入高三位 00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//TIDYING 2状态放入高三位 01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//TERMINATED 3状态放入高三位 01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//根据int值的低29位解析出状态,~COUNT_MASK=11100000000000000000000000000000
private static int runStateOf(int c) {
return c & ~COUNT_MASK;
}
//根据int值的高3位解析出计数,COUNT_MASK=00011111111111111111111111111111
private static int workerCountOf(int c) {
return c & COUNT_MASK;
}
//反向解析,传入运行状态和计数,得到int值
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
在学习ThreadPoolExecutor之前,需要先了解下此类源码依赖的基础知识。
三。CAS的基础知识
CAS,多个线程并发设置新值,通过传入老值,只有一个线程可以设置成功。JAVA提供Atomic相关类,比如AtomicBoolean的基本语法如下:
AtomicBoolean dealing = new AtomicBoolean();
//当原值是false的时候,设置为true,返回值是是否成功
dealing.compareAndSet(false, true);
下边用10个 线程了并发来设置,只有一个线程能够返回true
AtomicBoolean dealing = new AtomicBoolean();
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
System.out.println(dealing.compareAndSet(false, true));
}
}.start();
}
输出:
false
true
false
false
false
false
false
false
false
false
四。位操作结合CAS在ThreadPoolExecutor中应用
CAS虽然是控制一个值的并发,但ThreadPoolExecutor把一个整数的不同位拆开来用,当作了两个变量。那么CAS就能达到控制两个变量并发安全的效果。在CAS看起来是一个变量,但却帮助程序两个变量一起实现了并发控制,不会有意外的多线程覆盖问题,两个变量类似做了事务,全部成功或全部失败。比如在advanceRunState方法中的用法:
//ctl的值倍拆分为了两部分
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private void advanceRunState(int targetState) {
// assert targetState == SHUTDOWN || targetState == STOP;
for (; ; ) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
//同时设置状态和计数,并发安全的事务保证
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}