之前讲过的AtomicInteger等CAS操作会产生ABA问题,什么是ABA?wiki官方解释https://en.wikipedia.org/wiki/ABA_problem,简单讲就是多线程环境,2次读写中一个线程修改A->B,然后又B->A,另一个线程看到的值未改变,又继续修改成自己的期望值。如果我们不关心中间状态的变化,只关心最终结果,就无所谓ABA问题。看代码:
import java.util.concurrent.atomic.AtomicReference; public class ABATest { static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(1); public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(1, 2)); System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(2, 1)); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+ "-" + atomicReference.compareAndSet(1, 2)); } }).start(); Thread.currentThread().sleep(3000); System.out.println(atomicReference.get()); } }一个线程将1->2->1,另一个线程看到期望值还是1,就将其改成最终值2,如果不关心第一个线程所做的中间状态的变更,也就不用关心ABA问题。 这是一个不是"问题"的问题。
看下AtomicStampedReference是怎么解决这个问题的:
/** 通过static pair保存一个引用和计数器 */ private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; /** * 通过传入的初始化引用和计数器来构造函数一个pair * * @param initialRef 初始化用用 * @param initialStamp 初始化计数器或者叫时间戳 */ public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); }
举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。这就是AtomicStampedReference的解决方案。
看下AtomicStampedReference的方法:
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; //每次操作前不但比较引用值还比较计数器,底层还是unsafe那些方法 return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
public V getReference() { return pair.reference; } public int getStamp() { return pair.stamp; }
import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicStampedReferenceTest { static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(0, 0); public static void main(String[] args) throws InterruptedException { final int stamp = atomicStampedReference.getStamp(); final Integer reference = atomicStampedReference.getReference(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread() + "-" + reference + "-" + stamp + "-" + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1)); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { Integer reference = atomicStampedReference.getReference(); System.out.println(Thread.currentThread() + "-" + reference + "-" + stamp + "-" + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1)); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(atomicStampedReference.getReference()); System.out.println(atomicStampedReference.getStamp()); } }
还是那个水的例子,AtomicStampedReference可能关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过,方法都比较简单。
参考:
http://brokendreams.iteye.com/blog/2250127
http://my.oschina.net/u/1011494/blog/535945
http://my.oschina.net/u/1011494/blog/540964?fromerr=iSkJ3wjX