AtomicStampedReference源码详解
通过前面几个原子类源码的学习,可以发现这些原子类型实现原子类型的操作,主要是利用volatile和CAS来实现的。其中,volatile关键字可以保证线程可见性,而CAS算法,主要是通过unsafe,利用CPU的指令来实现操作的原子性,CAS算法实现了一种快速失败的方式,当某个线程修改已经被改变的数据时,会快速失败。另外,当CAS算法修改某个数据失败时,由于有自旋算法的加持,对于数据的修改最终会成功。在大多数情况下,通过CAS算法来实现操作的原子性,是没有问题的。唯一例外的情况,就是ABA的问题。
如果,某个数据的值,从A变成B,然后从B由变成A。此时,CAS算法在进行检查的时候,发现数据没有变化,然后就执行更新操作。
但是,实际上,数据是发生了变化的。针对,这种情况,一般是给数据增加一个版本号的属性,当数据发生改变的时候,对版本号也做相应的更新。这样,就可以解决ABA的问题,这就是AtomicStampedReference的使用场景。
类定义
public class AtomicStampedReference<V>{
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);
}
}
}
AtomicStampedReference:在内部定义了一个内部类Pair,主要用来存放引用值和版本号stamp.
属性定义
private volatile Pair<T> pair;
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset = objectField(UNSAFE, "pair", AtomicStampedReference.class);
构造函数
public AtomicStampedReference(V initialRef, int initialStamp){
pair = Pair.of(initialRef, initialStamp);
}
工具方法
private boolean casPair(Pair<V> cmp, Pair<V> val){
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
static long objectField(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz){
try{
return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
}catch(NoSuchFieldException e){
NoSuchFieldError error = new NoSuchFieldError(field);
error.initCause(e);
throw error;
}
}
get和set方法
public V getReference(){
return pair.reference;
}
public int getStamp(){
return pair.stamp;
}
public V get(int[] stampHolder){
Pair<V> pair = this.pair;
stampHolder[0] = pair.stamp;
return pair.reference;
}
public void set(V newReference, int newStamp){
Pair<V> current = pair;
if(newReference != current.reference || newStamp != current.stamp){
this.pair = Pair.of(newReference, newStamp);
}
}
这里,需要重点说下方法get(int[] stampHolder)方法,这个方法的意图是获取当前值以及stamp值,但是Java不支持多值的返回,并且在AtomicStampedReference内Pair被定义为私有的,因此在此处就采用了传参的方式来解决这个问题。
比较设置方法
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp){
Pair<V> current = pair;
return expectedReference == current.reference &&
expectStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
public boolean weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp){
return compareAndSet(expectedReference, newReference, expectedStamp, newStamp);
}
compareAndSet方法,主要是比较并且设置当前的引用值,这和其他原子类的CAS算法相同。这里,唯一区别的地方,是需要去比较版本号,即stamp的值。当expectedReference与当前的引用值相等,并且,expectedStamp与当前的stamp相等时,执行更新操作,把当前的引用值设置为newReference,当前的版本号设置为newStamp,然后返回true,否则,返回false.
更新版本号方法
public boolean attempStamp(V expectedReference, int newStamp){
Pair<V> current = pair;
return expectedReference == current.reference &&
(newStamp == current.stamp || casPair(current, Pair.of(expectedReference, newStamp)));
}
该方法,主要是原子性地更新版本号的值,首先会比较当前引用的对象是不是expectedReference,如果是,则更新版本号的值,并且返回true,否则,返回false.