谈谈对CAS的理解

一、基本概念

什么是CAS?

CAS是Compare And Swap比较并交换的简称,主要发生在java的JUC的原子类操作的底层实现中;

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和要修改的新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

简单验证Demo

package com.jiguiquan.www;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1、什么是CAS?
 * 	比较并交换(Compare And Swap)
 * @author jiguiquan
 *
 */
public class CASDemo {
	public static void main(String[] args) {
		AtomicInteger atomicInteger = new AtomicInteger(3);
		
		boolean flag1 = atomicInteger.compareAndSet(3, 666);  //3位期望值,666位目标值
		System.out.println(flag1+"\t current data: "+atomicInteger);
		
		boolean flag2 = atomicInteger.compareAndSet(3, 888);
		System.out.println(flag2+"\t current data: "+atomicInteger);
	}
}

运行结果如下:

image.png

很显然,第一次由于期望值和内存位置当前值一样,都为3,那么可执行修改,将值修改为666;

但是第二次,由于此时内存位置的值已经被修改为666了,与它所期望的值3不一致,那么修改不会被执行,所以值依旧保持为666;

由此,很轻易地验证了CAS机制;


二、CAS的底层原理(难点)

核心两点:

  • Unsafe类

  • 自旋锁

1、谈谈对Unsafe类的理解:

JUC中的原子类之所以能够保证原子性,就是依靠的底层的Unsafe类;

这里我们借助 AtomicInteger.getAndIncrement() 方法去追踪它的源码:

image.png

getAndIncrement()方法:

/**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

AtomicInteger原子类中使用的核心变量:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 这里引入了Unsafe类
    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); }
    }
    
    //此处的value值使用了volatile修饰符,确保了每一次的操作后的value值对其他线程可见
    private volatile int value;
    ...
}

Unsafe中的getAndAddInt()方法

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
    {
      i = getIntVolatile(paramObject, paramLong);
    } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
    //compareAndSwapInt是一个本地方法,该方法的实现位于unsafe.cpp中,即使C++代码,其中使用了CPU原语汇编,确保了原子性;
    return i;
  }

Unsafe类:

1.1、Unsafe类是CAS的核心类,由于Java无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。

Unsafe类存在于 rt.jar 包下的 sum.misc 包中,其累不操作可以像C的指针一样直接操作内存,因为Java中的CAS操作的执行依赖于Unsafe类的方法;

做个比喻:相当于,“我即使不知道教室XXX的名字,我可以直接用位置描述,第三排第五位同学站起来报一下你的年龄;”——直接用位置来操作

注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

1.2、变量valueoffset,表示该变量在内存中的偏移量(即在内存中的地址位置),因为Unsafe类就是根据这个内存偏移量来直接读取数据的,非常精确,所以这个值是必须的;

因为已经跨语言了,总不能把java中的对象传递给C/C++,它们也不认识,所以只能是通过内存中的偏移量(位置)来传递需要操作的对象

image.png

1.3、变量value使用volatile修饰符修饰,确保了在多线程之间的内存可见性;

梳理一下整个过程的执行流程:

假设线程A和线程B两个线程同时执行getAndAddInt操作(直接点,分别跑在不同的CPU上面):

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

2、线程A通过getIntVolatile(var1,var2)拿到了value值为3,此时线程A被挂起;

3、线程B通过getIntVolatile(var1,var2)拿到了value值为3,但是线程B没有被挂起,并顺利执行了compareAndSwapInt方法,比较内存值也为3,成功修改内存值到4,线程B打完收工,一切OK;

4、这时线程A恢复,执行compareAndSwapInt方法时发现,自己手里的预期值3和此时主内存中的4不一致,说明该值已经被其他线程抢先一步修改过了,那么A线程本次修改失败,只能重新读取,重新再来一遍;

5、线程A重新获取value值,因为变量value被volatile修饰,所以B线程对于value的修改,A线程是能看到的,线程A继续执行compareAndSwapInt进行比较替换,直到成功;


2、自旋;

AtomicIntger能够实现原子性的底层原理就是:Unsafe类+CAS思想;

比较并交换,直到交换成功,这种操作就被称为自旋,关于自旋锁,后面的章节会详细讲解;

经过了上面的解释,这里再次对CAS做一个总结:

CAS全称为 Compare-And-Swap,它是一条CPU并发原语

它的功能是判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮助我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作

再次强调,由于CAS是系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用语完成某个功能的一个过程;并且,原语的执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题;也不会被其他线程加塞(必定线程安全)(好比如:习大大出行,5部车出门,中间肯定是不允许被其他车加塞的,根本不用我们担心);


三、CAS的缺点

优点:没有使用synchronized锁,即保证了原子性,又保证了并发性,是一个比较折中的办法,synchronized虽然简单,但是牺牲了并发性;

1、循环时间长,开销大

    因为CAS比较交换操作,使用了do…while…循环,只有在成功后才跳出循环,如果不成功则继续循环,如果CAS长时间一直不成功,可能会给CPU带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环);

2、只能保证一个共享变量的原子操作;

    对于多个共享变量的操作,循环CAS就无法保证操作的原子性了,只能考虑使用加锁的方式来保证一组、一段代码的原子性;

3、引出了新的ABA问题(狸猫换太子);

ABA问题在下一篇介绍!

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐