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返回失败。
全部评论