Compare and Swap

2020-03-12  本文已影响0人  wbpailxt

什么是CAS

是一种思想,是一种实现线程安全的算法,同时也是一条CPU指令,比如Compare and Swap这一条指令就能完成“比较并交换”原子操作。

CAS有三个操作数:内存值V、预期值A、要修改的值B,当且晋档预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。最后返回现在的V值。
用大白话说就是:我任务V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那我就不修改了。
这种思想最终通过调用CPU的一些特殊指令来实现,这些指令由CPU保证了“比较和交换”两个动作的原子性。

分析在java中是如何利用CAS的CPU指令实现“比较和交换”原子操作

让我们来看看AtomicInteger是如何通过CAS实现并发下的累加操作的,以AtomicInteger的getAndAdd方法为突破口。

getAndAdd方法

public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

可以看出,这里使用了Unsafe这个类,这里需要简要介绍一下Unsafe类:

Unsafe类

Unsafe是CAS的核心类。Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。

AtomicInteger加载Unsafe工具,用来直接操作内存数据

public class AtomicInteger extends Number implements java.io.Serializable {
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public final int get() {
        return value;
    }

在AtomicInteger数据定义的部分,我们还获取了unsafe实例,并且定义了valueOffset。再看到static块,懂类加载过程的都知道,static块的加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value值在内存中的偏移地址。
有了value在工作内存中的偏移地址之后,我们就可以用unsafe直接操作这个地址了,通过这个地址我们可以获取原值,也可以写入新值。
因为是修改工作内存中的value值,所以value是用volatile修饰的,保证了多线程之间不会出现可见性的问题。

接下来继续看Unsafe的getAndAddInt方法的实现

// AtomicInter类
public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
}
// Unsafe类
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;
}

我们看var5获取的是什么,通过调用unsafe的getIntVolatile(var1, var2),这是个native方法,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是我们前面提到的valueOffset,这样我们就从内存里获取到现在valueOffset处的值了。

现在重点来了,compareAndSwapInt(var1, var2, var5, var5 + var4)其实换成compareAndSwapInt(obj, offset, expect, update)比较清楚,意思就是如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作。

加法意味着只要在最新的值上加上我要加的值即可,我先取出工作内存中最新的值(有volatile保证最新),然后进行“比对并交换”原子操作。比对成功说明我刚从工作内存中取出的值是最新的,可以往该值加上我要加的值。比对不成功,说明我刚刚取出工作内存中的值的确是最新的,但不幸过后就被别的线程修改了,我比对的这一刻已经不是最新的了,所以我不能往该值加上我要加的值(加的话会导致加少),只能再次去工作内存中获取最新的值再加上,不释放CPU,也就是在自旋。
-->自旋+cas = atomicInteger

Unsafe类中的compareAndSwapInt本地方法

compareAndSwapInt.cpp
根据偏移量拿到value值在工作内存中的地址
通过Atomic:cmpxchg实现原子性的比较和替换,其中x是即将更新的值,e是原内存的值。

CAS思想在别的地方的体现

并发容器concurrenthashmap的put操作

缺点

上一篇 下一篇

猜你喜欢

热点阅读