ABA問題
比如有兩個線程,線程T1和線程T2。線程T1的執行時間短(假如需要2s),線程T2的執行時間長(假如需要4s),也即是線程T2執行一次的時間,線程T1可以執行多次。
當線程T1將變量從A變成B,又從B變成A,這對線程T2而言,它是不知道該變量是否發生過變化的。可能會導致不可預知的問題。
通過原子類舉例
public static void main(String[] args) {
AtomicInteger num=new AtomicInteger(0);
//如果num的當前值是0 則把它更新爲1
System.out.println(num.compareAndSet(0, 1));
//如果num的當前值是1 則把它更新爲0
System.out.println(num.compareAndSet(1, 0));
//打印num當前值
System.out.println(num.get());
}
num的數據從0–>1,又從1–>0。不能說num沒有變化過。
ABA問題的解決
通過借鑑樂觀鎖的思路,爲數據添加上版本號來處理這個問題。JUC包中也提供了對應的解決方案AtomicStampedReference
。這個類維護了一個“版本號”Stamp,在進行CAS操作的時候,不僅要比較當前值,還要比較版本號。只有兩者都相等,才執行更新操作。
public class StampDemo {
private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
try {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本號: " + stamp);
Thread.sleep(2 * 1000);
stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本號: " + stampedReference.getStamp());
stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第一次版本號: " + stampedReference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
Thread.sleep(4 * 1000);
boolean result = stampedReference.compareAndSet(100, 2019, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功與否:" + result + " 當前最新版本號" + stampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t當前實際值:" + stampedReference.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
輸出結果:
T1 第一次版本號: 1
T1 第二次版本號: 2
T1 第一次版本號: 3
T2 修改成功與否:true 當前最新版本號4
T2 當前實際值:2019