AtomicStampedReference阅读笔记
问题
1、ABA是什么?
-
CAS情况下会导致这个发生。
ABA问题发生在多线程环境中,当某线程连续读取同一块内存地址两次,两次得到的值一样,它简单地认为“此内存地址的值并没有被修改过”,然而,同时可能存在另一个线程在这两次读取之间把这个内存地址的值从A修改成了B又修改回了A,这时还简单地认为“没有修改过”显然是错误的。
2、ABA的危害?
- 这篇文章的ABA危害写的很好
- 上面主要用一个栈来模拟,一个first线程cas操作时堵塞了,另外一个second线程去把栈进行B操作 ,再把原值继续改回A,结果first线程cas判定成功
3、ABA的解决方案?
4、AtomicStampedReference是怎么解决ABA的?
- 使用版本号解决(自定义内部类Pair)
一、简介
优点:
缺点:
二、继承关系图
无
三、存储结构
自定义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);
}
}
//将元素值和版本号绑定在一起,存储到reference 和stamp中
属性
private volatile Pair<V> pair;
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
//获取pair变量在对象中的内存偏移量
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
构造
//传入初始值和初始版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
主要方法:compareAndSet()方法
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
//预期的值和当前值是否一样 &&
expectedReference == current.reference &&
//预期的版本号和当前的版本号一样 &&
expectedStamp == current.stamp &&
(
//新值和当前值一样且新的版本和当前值一样,则不做变化,返回即可
(newReference == current.reference &&
newStamp == current.stamp) ||
//cas替换,Unsafe.compareAndSwapObject,比较和替换对象
casPair(current, Pair.of(newReference, newStamp))
);
}
使用方式
public static void main(String[] args) throws InterruptedException {
AtomicStampedReference<String> atomic = new AtomicStampedReference<>("aaa",1);
//获取元素值(pair.reference)
System.out.println(atomic.getReference());//"aaa"
//获取版本号(pair.stamp)
System.out.println(atomic.getStamp());//1
//不修改元素值,只修改版本,
boolean isOk = atomic.attemptStamp("aaa",atomic.getStamp() + 1);
System.out.println(isOk);//true
System.out.println(atomic.getStamp());//2
//修改元素值和版本号
boolean isOk1 = atomic.compareAndSet(atomic.getReference(),"bbb",atomic.getStamp(),atomic.getStamp() + 1);
System.out.println(isOk1);//true
System.out.println(atomic.getReference());//"bbb"
System.out.println(atomic.getStamp());//3
//与compareAndSet一样,因为内部直接调用的compareAndSet方法
boolean isOk2 = atomic.weakCompareAndSet(atomic.getReference(),"ccc",atomic.getStamp(),atomic.getStamp() + 1);
System.out.println(isOk2);//true
System.out.println(atomic.getReference());//"ccc"
System.out.println(atomic.getStamp());//4
//获得版本号和元素值
int [] stamp = new int[1];
String reFenence = atomic.get(stamp);
System.out.println("stamp:"+stamp[0]);//stamp:4
System.out.println("reFenence:"+reFenence);//reFenence:ccc
}
五、总结
1、在多线程下使用无锁结构需要注意ABA问题
2、ABA的解决一般使用版本号来控制,并且保证数据结构使用元素值来传递,且每次添加元素都新建节点承载元素值。
3、AtomicStampedReference采用内部Pair类来存储元素值和版本号。
额外:
AtomicMarkableReference 也可以解决ABA问题,他不维护版本号,使用的是一个boolean类型的标记