位操作与使用场景示例(ThreadPoolExecutor)

159 阅读6分钟

在学习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;
        }
    }