理解CAS

2020年8月1日
理解CAS插图

本文出自明月工作室:https://www.freebytes.net/it/java/learn-cas.html

众所周知,synchronize能够保证数据操作的原子性,从而实现同步效果。但是这种通过多个线程竞争锁的方式其效率会比较差,于是java另外实现了另一种无锁的却能保证数据操作原子性的方式——CAS(Compare-and-swap)。

Compare-and-swap,是一个针对非抢占式微处理器的一条指定指令的宽泛术语,这条指令读取内存的位置,比较独到的值和期望的值,当读到的值等于期望的值时,就将新值写入到该内存位置;否则,不做理会。

说的通俗点就是,线程在修改一个变量值时,首先判断这个变量有没有被修改过,如果没有,那就执行修改。

比如:多个线程同时修改一个值(count=0)时,每个线程的工作内存中都有这个值的副本(count=0),当线程开始对其进行修改时,先把0作为一个期望值,与主内存中count的实际值相比较,如果主内存中的值确实等于期望值,那么才可以进行对这个值的修改,并将修改后的值再次写入主内存中,这便是一次比较并交换的过程;然而如果期望值不等于实际值,该线程需要重新从主内存中获取count的值,以便用于下一次的比较交换操作。

下面通过实际例子加深理解——

现在有10000个线程对变量int i = 0进行i++操作,如果不加任何同步,那么有可能最终打印出的i的值是小于10000的数字。

public class Test {

    static int count = 0;

    public static void main(String[] argv) {
        for (int i = 0; i < 10000; i++) {
            TestThread thread = new TestThread();
            thread.start();
        }
        while (Thread.activeCount()>1){
        }
        System.out.println(count);
    }
}

class TestThread extends Thread {
    @Override
    public void run() {
        Test.count++;
    }
}

输出:
9999

最简单的解决方法就是给线程类的Test.count++语句加上synchronize锁,但CAS是如何解决的呢?

这是一个以CAS作为底层的原子类AtomicInteger的应用案例——

public class TestCAS {
    static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            TestCASThread thread = new TestCASThread();
            thread.start();
        }
        while (Thread.activeCount()>1){
        }
        System.out.println(count.get());
    }
}

class TestCASThread extends Thread {
    @Override
    public void run() {
        TestCAS.count.incrementAndGet();
    }
}

输出:
10000

incrementAndGet()方法,是递增1的意思。它的底层调用的是Unsafe类的compareAndSwapInt()方法——

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

由于这个Unsafe类没有源码,所以只能看到class文件的反编译代码,挺难看的,但能够看出,它在循环访问compareAndSwapInt()方法,进行重复的比较交换,比较交换成功之后才返回值。

compareAndSwapInt() 方法以原子的方式执行下列指令的序列:

//获取内存中的实际值
int readValue = value;
//将期望值与实际值进行比较,如果相同,就将其修改
if( readValue == expectedValue ){
   value = newValue;
}
//期望值与实际值不同,证明该值已被其他线程修改,返回实际值
return readValue;

也就是说,CAS的读-改-写序列,是一个原子性操作,也只有这样,才能保证线程在对变量进行比较交换的过程中,不会出现脏读的情况。