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類型的標記