CAS的全称是compare and swap,它是java同步类的基础,java.util.concurrent中的同步类基本上都是使用CAS来实现其原子性的。java
CAS的原理其实很简单,为了保证在多线程环境下咱们的更新是符合预期的,或者说一个线程在更新某个对象的时候,没有其余的线程对该对象进行修改。在线程更新某个对象(或值)以前,先保存更新前的值,而后在实际更新的时候传入以前保存的值,进行比较,若是一致的话就进行更新,不然失败。git
注意,CAS在java中是用native方法来实现的,利用了系统自己提供的原子性操做。程序员
那么CAS在使用中会有什么问题呢?通常来讲CAS若是设计的不够完美的话,可能会产生ABA问题,而ABA问题又能够分为两类,咱们先看来看一类问题。github
更多精彩内容且看:算法
更多内容请访问www.flydean.com编程
咱们考虑下面一种ABA的状况:多线程
上面的例子中CAS成功了,可是实际上这个CAS并非原子操做,若是咱们想要依赖CAS来实现原子操做的话可能就会出现隐藏的bug。编程语言
第一类问题的关键就在2和3两步。这两步咱们能够看到线程b直接替换了内存地址X中的内容。post
在拥有自动GC环境的编程语言,好比说java中,2,3的状况是不可能出现的,由于在java中,只要两个对象的地址一致,就表示这两个对象是相等的。区块链
2,3两步可能出现的状况就在像C++这种,不存在自动GC环境的编程语言中。由于能够本身控制对象的生命周期,若是咱们从一个list中删除掉了一个对象,而后又从新分配了一个对象,并将其add back到list中去,那么根据 MRU memory allocation算法,这个新的对象颇有可能和以前删除对象的内存地址是同样的。这样就会致使ABA的问题。
若是咱们在拥有自动GC的编程语言中,那么是否仍然存在CAS问题呢?
考虑下面的状况,有一个链表里面的数据是A->B->C,咱们但愿执行一个CAS操做,将A替换成D,生成链表D->B->C。考虑下面的步骤:
最后咱们的到的链表是D->C,而不是D->B->C。
问题出在哪呢?CAS比较的节点A和最新的头部节点是否是同一个节点,它并无关心节点A在步骤1和3之间是否内容发生变化。
咱们举个例子:
public void useABAReference(){
CustUser a= new CustUser();
CustUser b= new CustUser();
CustUser c= new CustUser();
AtomicReference<CustUser> atomicReference= new AtomicReference<>(a);
log.info("{}",atomicReference.compareAndSet(a,b));
log.info("{}",atomicReference.compareAndSet(b,a));
a.setName("change for new name");
log.info("{}",atomicReference.compareAndSet(a,c));
}
复制代码
上面的例子中,咱们使用了AtomicReference的CAS方法来判断对象是否发生变化。在CAS b和a以后,咱们将a的name进行了修改,咱们看下最后的输出结果:
[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true
[main] INFO com.flydean.aba.ABAUsage - true
复制代码
三个CAS的结果都是true。说明CAS确实比较的二者是否为统一对象,对其中内容的变化并不关心。
第二类问题可能会致使某些集合类的操做并非原子性的,由于你并不能保证在CAS的过程当中,有没有其余的节点发送变化。
第一类问题在存在自动GC的编程语言中是不存在的,咱们主要看下怎么在C++之类的语言中解决这个问题。
根据官方的说法,第一类问题大概有四种解法:
第二类问题其实算是总体集合对象的CAS问题了。一个简单的解决办法就是每次作CAS更新的时候再添加一个版本号。若是版本号不是预期的版本,就说明有其余的线程更新了集合中的某些节点,此次CAS是失败的。
咱们举个AtomicStampedReference的例子:
public void useABAStampReference(){
Object a= new Object();
Object b= new Object();
Object c= new Object();
AtomicStampedReference<Object> atomicStampedReference= new AtomicStampedReference(a,0);
log.info("{}",atomicStampedReference.compareAndSet(a,b,0,1));
log.info("{}",atomicStampedReference.compareAndSet(b,a,1,2));
log.info("{}",atomicStampedReference.compareAndSet(a,c,0,1));
}
复制代码
AtomicStampedReference的compareAndSet方法,多出了两个参数,分别是expectedStamp和newStamp,两个参数都是int型的,须要咱们手动传入。
ABA问题实际上是由两类问题组成的,须要咱们分开来对待和解决。
本文的例子github.com/ddean2009/ learn-java-base-9-to-20
本文做者:flydean程序那些事
本文连接:www.flydean.com/aba-cas-sta…
本文来源:flydean的博客
欢迎关注个人公众号:程序那些事,更多精彩等着您!