AtomicXXX可以通過CAS(Compare And Set)機制進行原子操作。
但是存在ABA問題。
舉例:
線程T1想修改100為101,
而在T1發出compareAndSet指令之前,有T2將100修改為了99,又改回了100,
此時T1發出compareAndSet指令,發現100還是100,復合條件,所以修改100為101。
然而此時已經不是之前的現場了。
問題代碼:
private static AtomicInteger atomicInt = new AtomicInteger(100);
Thread intT1 = new Thread(new Runnable() {
@Override
public void run() {
atomicInt.compareAndSet(100, 99);
atomicInt.compareAndSet(99, 100);
}
});
Thread intT2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
boolean c3 = atomicInt.compareAndSet(100, 101);
System.out.println(c3); // true
}
});
intT1.start();
intT2.start();
intT1.join();
intT2.join();
此時輸出:
true
可以通過AtomicStampedReference類增加版本號解決該問題,在設置值的同時加上期望的版本號和新的版本號,
代碼如下:
private static AtomicInteger atomicInt = new AtomicInteger(100);
private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);
Thread refT1 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
atomicStampedRef.compareAndSet(100, 99, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
atomicStampedRef.compareAndSet(99, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
}
});
Thread refT2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = atomicStampedRef.getStamp();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
System.out.println(atomicStampedRef.getStamp());
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(c3); // false
}
});
refT1.start();
refT2.start();
refT1.join();
refT2.join();
輸出如下
2
false
refT1中的sleep保證refT2可以獲取修改前的stamp=1,
refT2中的sleep保證refT1能完成修改后再進行修改,
此時,refT2期望的stamp為0,但是此時獲取的stamp已經是2,所以CAS返回失敗。