CAS 是什么

CAS (Compare And Swap) 比较并交换html

CAS 是一条CPU并发原语。功能是判断内存某个位置的值是否为预期值,若是是则更改成最新值,这个过程是原子的。CAS并发原语体如今Java语言中就是sun.misc.Unsafe类中的各个本地方法。这是一种彻底依赖于硬件的功能,经过它实现了原子操做。原语的执行时连续的,在执行过程当中不容许被中断,也就是说CAS是一条CPU的原子指令,不会形成数据不一致。java

CAS的做用是比较当前工做内存中的值和主内存中的值,若是相同则执行规定操做,不然继续比较直到主内存和工做内存中的值一致为止。 若是单看这句话不知道在说什么,那么就继续往下看,通篇结束以后再看读一下这句话吧。安全

这一篇经过AtomicInteger中两个方法来探索CAS究竟是如何实现的。多线程

AtomicInteger的两个方法

compareAndSet

先来看三行代码并发

AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,10)+ "\t current data : " +atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,9)+ "\t current data : " +atomicInteger.get()); 
复制代码

上篇文章对volatile的理解 提到了,可使用原子类AtomicInteger 解决原子性问题。这里用到了AtomicInteger.compareAndSet方法,两个参数分别是指望值更新值,若是指望值与此时内存中的值相同,则将内存中的值更新为更新值,方法返回值为布尔类型,表示是否更新成功。以上三行代码,初始化时设置内存中值为5,第二行指望值与更新值分别为5和10,若是指望值5与内存中的值5相同,则将内存中的值更新为10,第二行返回true,当前内存中的值为10;第三行指望值为5,当前内存中的值为10,不符合指望结果,则不更新内存中的结果,返回false,当前内存中的值未更改,仍是10。代码输出结果以下:post

true	 current data : 10
false	 current data : 10
复制代码

概述一下,CAS涉及到3个操做数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。this

getAndIncrement

咱们知道在多线程状况下i++操做其实包含了三个步骤,是线程不安全的。咱们可使用atomicInteger.getAndIncrement() 实现线程安全的i++,下面经过看下该方法的实现了解如何作到线程安全。atom

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

以上是getAndIncrement的源代码,注释在当前值的基础上原子增长一。实现为调用unsafe类的getAndAddInt方法,该方法有三个参数,分别是当前对象、内存偏移量、1,再往下看,就进入到unsafe类。spa

  1. unsafe类
    unsafe类是CAS的核心类。java是没法直接访问底层系统的,须要经过本地(native)方法来访问,Unsafe的全部方法都是native来修饰的,也就是说Unsafe类中的基础方法都直接调用操做系统底层资源执行相应任务,便可以像C的指针同样直接访问内存。
  2. 变量valueOffset,表示该变量值在内存中的偏移地址,由于Unsafe就是根据内存偏移地址获取数据的。如下代码给出了valueOffset的来源
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;
复制代码
  1. 变量value用volatile修饰,保证了多线程之间内存可见性。所以拿到的内存地址老是最新的。

了解这几个概念,咱们继续往下看,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;
    }
复制代码

这仍然是存在于unfase的方法,var1对应this,即当前对象,var2为内存偏移量,var4便是要添加的“1”。
当前方法为do-while结构,根据方法名中getAndAdd,首先get操做,do结构首先根据对象与内存地址获取当前值var5,从物理主内存中拷贝到本身的线程工做内存;而后关键点来了:while里面判断若是这一时刻内存地址中的值与var5相同,则修改成var5+var4,即Add成功,这时返回true,while取反,条件不成立,再也不执行do;若是刚拿到var5的值,主物理内存中的值就发生变化了,即主内存与var5的值不一致,则不修改,返回false,while条件知足,继续执行do操做,再次获取当前内存中最新的值,继续判断,直至保证加一时拿到的结果时最新的。
以上操做简单说,就是拿到var5,而后对var5+1,担忧加一操做时var5的值已经发生变化,因此加一时要再验证一遍,若是这个内存中的值仍是var5那么就放心的加一操做,不然就不停的去获取最新的值。

下面举个例子来具体解释上一段内容:

假设线程A和线程B两个线程同时执行getAndAddInt操做:

  1. AtomicInteger里面的value原始值为3,即主内存中AtomicInteger中的value为3,根据JMM模型(参考上篇文章对volatile的理解),线程A和线程B各自持有值为3的value副本分别到各自的工做内存。
  2. 线程A经过getIntVolatile(var1,var2)拿到value值为3,这是A被挂起。
  3. 线程B也经过getIntVolatile(var1,var2)拿到value值为3,此时恰好相线程B没有被挂起并执行compareAndSwapInt方法比较内存值也是3,成功修改内存值为4,线程B完成操做。
  4. 这时线程A恢复,并执行compareAndSwapInt方法,发现本身工做内存中的数字3与主内存中的数字4不一致,说明该值已经被其余线程抢先修改过了,那么A线程本次修改失败,从新读取最新值重来一遍,即从新执行do操做。
  5. 线程A从新获取value值,由于变量value被volatile修饰,因此其余线程对他的修改,线程A总能第一时间看到,线程A执行compareAndSwapInt进行比较替换,直至成功。

再概述一下,CAS有3个操做数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。 这时再去看开篇的那句话是否就理解了呢。

CAS的缺点

  1. 循环时间长开销很大
    • 根据源码能够看到,内部采用do-while结构,若是while条件不知足则一直尝试获取新的值,因为CAS没有锁,可能有多个线程同时来修改内存中的值,当多个线程同时在do代码块中不断循环时,给CPU带来很大的开销。而synchronized则保证同一时间只有一个线程在真正修改内存中的值。
  2. 只能保证一个共享变量的原子操做
    • 因为getAndAddInt方法中的第一个参数是object,即this 表示当前对象,从数量上来讲这里只能控制一个共享变量。而synchronized加锁能锁住一段代码,能够保证多个线程都被锁住。
  3. 引出来ABA问题
    • ABA问题:线程A要将5修改成10,如今get到内存中的值为5,这是被挂起;线程B将内存中的值从5改成6,而后又从6改成5,B的一顿操做事后A获取资源继续进入while判断,发现内存中的值仍是5跟本身手里持有的值相同,能够进行更新操做将5更为10。在A看来get和set操做中间没有发生变化,get的是5,set的时候仍是5,实际上里面的值已经发生过变化了。尽管线程A的CAS操做成功,可是不表明这个过程就没有问题。
相关文章
相关标签/搜索