经典的ABA问题:
ABA问题存在CAS无锁方案中,我们写一个CAS的伪代码栗子:
class SimulatedCAS{ volatile int count; int newValue; //实count+=1 addOne(){ do { newValue=count+1; //① }while(count != cas(count,newValue) )//② } //模拟实现CAS,仅⽤来帮助理解 synchronized int cas( int expect, int newValue){ // 读⽬前count的值 int curValue=count; // ⽐较⽬前count值是否==期望值 if(curValue==expect){ // 如果是,则更新count的值 count=newValue; } // 返回写⼊前的值 return curValue; } }
假设 count原本是 A,线程 T1 在执行完代码①处之后,执行代码②处之前,有可能 count 被线程 T2 更新成了 B,之后又被 T3 更新回了 A,这样线程 T1 虽然看到的一直是 A,但是这个值其实已经被其他线程更新过了,这就是 ABA 问题。
如何解决:
最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。
Java中提供的原子化的对象引用类型AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。
1 public boolean compareAndSet(V expectedReference, V newReference, 2 int expectedStamp, int newStamp) { 3 Pair<V> current = pair; //获取当前pair 4 return 5 expectedReference == current.reference && //原始值等于当前pair的值引用,说明值未变化 6 expectedStamp == current.stamp && // 原始标记版本等于当前pair的标记版本,说明标记未变化 7 8 ((newReference == current.reference && newStamp == current.stamp) || // 将要更新的值和标记都没有变化 9 casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair 10 }