吃透CAS

什么是CAS

​ CAS是compare-And-Swap的缩写,意思是比较并交换它是一条cpu并发原语。它的功能是判断内存某个位置的值是否为预期值。如果是则更改为新的值。这个过程是原子的。因为原语的执行必须是连续的,在执行过程中不允许被中断。也就是说CAS是一条cpu的原子指令。不会造成数据不一致问题。

 

java中的魔法类sun.misc.Unsafe

​ Unsafe,先混个脸熟。Unsafe的底层实现是cas的核心类,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。想要吃透JUC下的AtomicXXX,Lock。Unsafe是绕不开的类。不求深入到底层的C/C++源码,但求能了解它的基本功能。查看Unsafe的源码我们会发现。我们直接调用会直接报错,因为Unsafe仅供java内部类使用。

@CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

不过我们可以想办法通过反射拿到Unsafe类的实例

 @Test
    public void test() throws NoSuchFieldException, IllegalAccessException {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        unsafe = (Unsafe) field.get(null);
    }

AtomicInteger示例

看看AtomicInteger在没有锁的情况下是如何做到数据正确性的。

static {
        try {
          //这里拿到了变量value的内存地址并赋值给valueOffset
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

//源码中,这是定义的变量值,用volatile原语,保证线程间的数据是可见的。这样才获取变量的值的时候才能直接读取
private volatile int value;

//不通线程可直接读取,因为value是volatile修饰
public final int get() {
        return value;
    }

然后来看看自增是怎么做到的并发安全的

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

//调用unsafe的getAndAddInt
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的本地方法,也是cas的核心
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Object var1:代表当前对象
long var2:代表内存偏移量,相当于对象值的引用地址
int var4:代表期望值,使用期望值和当前对象中的值进行比较
int var5:代表要交换的值

特别注意:compareAndSwapInt的实现原理是根据var1和var2算出内存中的真实值,和var4比较,为true则更新为var5。

因为特别重要,是最核心的东西举例再解释一次

假设A线程和B线程同时执行getAndAddInt操作:

1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据jmm模型,A线程和B线程各自持有一个值为3的value的副本到各自的工作内存。

2.线程A通过getIntVolatile(var1,var2)拿到var5的值3,这是A线程被挂起。

3.线程B也通过getIntVolatile(var1,var2)拿到var5的值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B完成执行。

4.这时线程A恢复,执行compareAndSwapInt方法,该方法通过前两个参数var1,var2发现自己的值3(var4)和主内存数字4(var4)不一致,返回false,那A线程本次修改失败,只能重读重取重新来一遍。

5.线程再循环一次getIntVolatile(var1,var2)拿到var5的值4,因为变量value被volatile修饰。所以其他线程对它的修改,线程A能够看到,线程A继续执行compareAndSwapInt方法比较,直到成功。

CAS的优点

1.避免加互斥锁
2.避免线程的切换提高程序运行效率

 

CAS的缺点

1.循环时间长开销大:因为有do while循环。如果cas失败,会一直尝试。高并发的场景,就有可能导致 CAS 一直都操作不成功,这样的话,循环时间就会越来越长。CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。所以根据实际情况来选择是否使用 CAS,在高并发的场景下,通常 CAS 的效率是不高的

解决方式:并发不算太高的时候选择CAS

2.只能保证一个共享变量的原子操作,不能同时保证多个对象的原子性

解决方案:利用一个新的类,来整合刚才这一组共享变量,这个新的类中的多个成员变量就是刚才的那多个共享变量,然后再利用 atomic 包中的 AtomicReference 来把这个新对象整体进行 CAS 操作,这样就可以保证线程安全。

3.重头戏:ABA问题 详情点击

上一篇:变量的缓存机制


下一篇:python3基础篇--字符串