Java CAS操做的ABA问题

转载自:https://my.oschina.net/OutOfMemory/blog/792289java

CAS介绍
比较并交换(compare and swap, CAS),是原子操做的一种,可用于在多线程编程中实现不被打断的数据交换操做,从而避免多线程同时改写某一数据时因为执行顺序不肯定性以及中断的不可预知性产生的数据不一致问题。算法

CAS操做基于CPU提供的原子操做指令实现,各个编译器根据这个特色实现了各自的原子操做函数。来源维基百科:编程

C语言:由GNU提供了对应的__sync系列函数完成原子操做。 
Windows:经过WindowsAPI实现了InterLocked Functions。
C++ 11:STL提供了atomic系列函数。
JAVA:sun.misc.Unsafe提供了compareAndSwap系列函数。
C#:经过Interlocked方法实现。
Go:经过import "sync/atomic"包实现。多线程

java.util.concurrent包彻底创建在CAS之上的,借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
能够看一下AtomicInteger:并发

public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

其中牵扯到3个值:current,next以及当前内存中的最新值,当且仅当current和内存中的最新值相同时,才会改变内存值为next。函数

CAS的ABA问题
ABA问题描述:
1.进程P1在共享变量中读到值为A
2.P1被抢占了,进程P2执行
3.P2把共享变量里的值从A改为了B,再改回到A,此时被P1抢占。
4.P1回来看到共享变量里的值没有被改变,因而继续执行。gradle

虽然P1觉得变量值没有改变,继续执行了,可是这个会引起一些潜在的问题。ABA问题最容易发生在lock free的算法中的,CAS首当其冲,由于CAS判断的是指针的地址。若是这个地址被重用了呢,问题就很大了。(地址被重用是很常常发生的,一个内存分配后释放了,再分配,颇有可能仍是原来的地址)。this

ABA问题解决方案
各类乐观锁的实现中一般都会用版本戳version来对记录或对象标记,避免并发操做带来的问题,在Java中,AtomicStampedReference也实现了这个做用,它经过包装类Pair[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。atom

下面看一下AtomicInteger和AtomicStampedReference分别执行CAS操做:spa

import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; public class ABASingle { public static void main(String[] args) { AtomicInteger atomicInt = new AtomicInteger(100); atomicInt.compareAndSet(100, 101); atomicInt.compareAndSet(101, 100); System.out.println("new value = " + atomicInt.get()); boolean result1 = atomicInt.compareAndSet(100, 101); System.out.println(result1); // result:true AtomicInteger v1 = new AtomicInteger(100); AtomicInteger v2 = new AtomicInteger(101); AtomicStampedReference<AtomicInteger> stampedRef = new AtomicStampedReference<AtomicInteger>( v1, 0); int stamp = stampedRef.getStamp(); stampedRef.compareAndSet(v1, v2, stampedRef.getStamp(), stampedRef.getStamp() + 1); stampedRef.compareAndSet(v2, v1, stampedRef.getStamp(), stampedRef.getStamp() + 1); System.out.println("new value = " + stampedRef.getReference()); boolean result2 = stampedRef.compareAndSet(v1, v2, stamp, stamp + 1); System.out.println(result2); // result:false } }

AtomicInteger 执行cas操做成功,AtomicStampedReference执行cas操做失败。

这样是否是就是说AtomicInteger存在ABA问题,根本就不能用了;确定是能够用的,AtomicInteger处理的一个数值,全部就算出现ABA问题问题,也不会有什么影响;可是若是这里是一个地址(地址被重用是很常常发生的,一个内存分配后释放了,再分配,颇有可能仍是原来的地址),比较地址发现没有问题,但其实这个对象早就变了,这时候就可使用AtomicStampedReference来解决ABA问题。

相关文章
相关标签/搜索