一、前言
AtomicInteger、AtomicLong等这类Atomic类都不能避免ABA问题,但项目中经常需要避免这类问题,此时可采用AtomicStampedReference进行辅助开发。AtomicStampedReference内部本身不能避免ABA,但它通过设置版本号,每次设置完成后对版本号进行比对,以此验证更新是否正确,不正确则重试。
二、源码解析
AtomicStampedReference是一个泛型类,并且没有继承任何方法和实现任何接口,内部通过一个内部类Pair对象来存储数据。
private static class Pair<T> {
//当前对象的引用
final T reference;
//当前版本号
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
//利用reference和stamp生成一个Pair对象,AtomicStampedReference中cas就是基于这个方法的
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
变量说明
//当前对象,包括对象引用和版本号
private volatile Pair<V> pair;
//unsafe常量,设置为使用Unsafe.compareAndSwapInt进行更新,方法中多处需要使用unsafe变量
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
//利用unsafe的本地方法来获取对象在内存的偏移地址
private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
构造方法
//构造方法利用initialRef和initialStamp生成当前对象,包括对象引用和版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
AtomicStampedReference中重要的方法
//利用unsafe的CAS方法来对对象进行交换和设置,AtomicStampedReference内部很多方法基于此方法实现的
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
常用方法
① getReference()方法。
//获取当前对象的引用
public V getReference() {
return pair.reference;
}
② getStamp()方法。
//获取当前对象的版本号
public int getStamp() {
return pair.stamp;
}
③ get(int[] stampHolder)方法。
//返回引用和戳记的当前值。
public V get(int[] stampHolder) {
Pair<V> pair = this.pair;
//将版本号赋值给数组的第一个元素
stampHolder[0] = pair.stamp;
//返回对象的引用
return pair.reference;
}
备注:我对参数 int[] stampHolder 的理解就是当参数是全局变量的时候,可以获取其stampHolder[0]的stamp值,不然局部变量的话就当这个方法结束运行这个局部变量就会跟着Java虚拟机栈的这个方法的栈帧清除。
④ compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)方法。
//利用expectedReference和newReference以及expectedStamp和newStamp进行cas
//解决的ABA问题
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
//获取当前对象
Pair<V> current = pair;
return
//判断当前对象和预期对象的引用是否一样
expectedReference == current.reference &&
//判断当前对象和预期对象的版本号是否一样
expectedStamp == current.stamp &&
//如果新的对象引用和当前对象引用一样 并且
//新的对象的版本号和当前对象的版本号一样 则 返回true
((newReference == current.reference &&
newStamp == current.stamp) ||
//或者通过cas方法将当前对象设置为新的对象,并且更新版本号
casPair(current, Pair.of(newReference, newStamp)));
}
⑤ set(V newReference, int newStamp)方法。
//对当前的对象更新为新的对象引用以及版本号
public void set(V newReference, int newStamp) {
Pair<V> current = pair;
//如果当前对象的引用不等于新的对象的引用 或者 新的版本号不等于当前对象的版本号
if (newReference != current.reference || newStamp != current.stamp)
//将当前对象设置为新的对象引用和版本号
this.pair = Pair.of(newReference, newStamp);
}
⑥ attemptStamp(V expectedReference, int newStamp)方法。
/**
* 如果当前引用是{@code ==},则自动将戳记的值设置为给定的更新值。
* 这个操作的任何给定调用都可能会错误地失败(返回{@code false}),
* 但是当当前值持有期望值并且没有其他线程也试图设置该值时,重复调用将最终成功。
* 一句话,就是更新当前对象的版本号
*/
public boolean attemptStamp(V expectedReference, int newStamp) {
Pair<V> current = pair;
return
//如果当前的对象引用等于预期对象的引用 并且
expectedReference == current.reference &&
//新的对象的版本号等于当前对象的版本号 则返回ture
(newStamp == current.stamp ||
//将当前对象设置为新的版本号和对象引用
casPair(current, Pair.of(expectedReference, newStamp)));
}
三、总结
个人觉得只要理解compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)方法,那么AtomicStampedReference就可以基本理解了。