java并发之CAS

写在最前面

在上文java并发之volatile末尾有提到,volatile并不能保证++操作的线程安全。我们来通过一个简单的例子看下为什么。

java并发之CAS_第1张图片
++测试demo

通过 javap -v看下其反编译后字节码指令:
java并发之CAS_第2张图片
反编译结果

从反编译结果可以看出,++被拆成了这样三个指令:

  1. getfield:获取变量count中的值;

  2. iadd:进行+1操作;

  3. putfield:将+1后的数据写回count。

volatile虽然可以保证变量的可见性,但是并不能保证这三个操作的原子性,所以它不能保证++操作的线程安全,那++操作的线程安全要怎么解决呢?

注:何谓原子性?
原子性是指一个操作不能被中断,即使是在多个线程一起执行的时候,一旦开始,也不会被其他线程打断。

  1. 第一种简单粗暴的方法就是加锁了,但是这样做开销有点大了耶;

  2. 第二种方法就是使用JDK的CAS了,juc包下也有相关Atomic类可以支持,使用简单,而且高效。(哈哈哈,终于绕回了本文的主题啊,不容易啊~~~)

接下来我们就通过分析CAS相关源码实现来看看CAS是如何做到线程安全的。

CAS原理

CAS,Compare and swap,比较和替换。CAS的操作包括三个参数:内存位置(V)、预期原值(A)和新的值(B),如果V和A相同,处理器会将该位置的值修改为B,否则,处理器不会做任何操作。

在前文java并发编程之原子类已经详细介绍了JDK提供的Atomic相关原子类的使用及实现,它们的实现都很神似,都是通过Unsafe提供的compareAndSwap开头的方法来完成对value的并发修改的。接下来我们就以compareAndSwapInt方法为例来看看它到底是怎么保证并发修改的安全性。

compareAndSwapInt实现

compareAndSwapInt声明

从声明可以看出,compareAndSwapInt是一个native的方法,该方法的实现位于unsafe.cpp中:

compareAndSwapInt实现.png

从源码可以看出,compareAndSwapInt主要做了这些事情:

  1. 获取value在内存中的地址;

  2. 调用Atomic::cmpxchg方法完成比较替换,其中e是原始值,x是新的值。

接下来我们来看看Atomic::cmpxchg的相关实现:

注:以linux x86平台为例,具体实现在atomic_linux_x86.inline.hpp中

java并发之CAS_第3张图片
Atomic::cmpxchg实现

在开始介绍源码之前先科普下这里用到的内嵌汇编的关键字:

  1. __asm__:用来声明一个内联汇编表达式,任何一个内联汇编表达式都是以它开头的,是必不可少的;

  2. __volatile__或者volatile__volatile__是GCC关键字volatile的宏定义,如果用了它,则是向GCC声明不允许对该内联汇编优化,否则当使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

有了这些科普我们再来看代码是不是就和谐多了,Atomic::cmpxchg其实就做了这样几件事:

  • 判断当前系统是否是多核处理器;

  • 依赖指令cmpxchg完成比较替换,如果当前系统是多核处理器,将会在指令前加lock前缀,否则不加。其中具体是否要加lock前缀的处理在宏LOCK_IF_MP中定义:

    LICK_IF_MP声明

lock前缀保证了CAS并发操作的安全性。

注:在上文java并发之volatile中已经详细介绍过lock前缀的意义,在这里我就不再赘述了,有需要了解的小伙伴请自行移步啦~

CAS存在的问题

CAS虽然可以很高效原子操作,但是它也是存在问题的。

  1. ABA问题
    因为CAS会在更新值的时候会检查值有没有发生改变,如果没有发生改变就更新,否则不更新,但是如果一个值原来是A,变成了B,再变回A,这时候CAS进行检查时会误认为它没有发生变化。针对这种情况,juc包提供了AtomicStampedReference,通过控制变量值的版本号来解决ABA问题。

  2. 循环时间长开销大
    自旋CAS如果长时间不成功会给CPU带来比较大的执行开销。

  3. 只能保证一个共享变量的原子操作
    对一个共享变量的操作,使用循环CAS肯定可以保证其原子性,但是如果对多个变量操作时,CAS就比较鸡肋了。当然有一个办法就是把几个共享变量合并成一个大的对象,然后CAS操作这个大对象。同样,juc包也提供AtomicReference用于更新对象。

你可能感兴趣的:(java并发之CAS)