CAS
CAS:Compare and Swap, 翻譯成比較并交換博其。
java.util.concurrent包中借助CAS實(shí)現(xiàn)了區(qū)別于synchronized同步鎖的一種樂(lè)觀鎖。
其原理是CAS有3個(gè)操作數(shù)挠将,內(nèi)存值V讹俊,舊的預(yù)期值A(chǔ)牵啦,要修改的新值B迫皱。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí)茎活,將內(nèi)存值V修改為B昙沦,否則什么都不做。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
ABA問(wèn)題
private static AtomicInteger atomicInt = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException {
Thread intT1 = new Thread(new Runnable() {
@Override
public void run() {
atomicInt.compareAndSet(100, 101);
log("thread intT1:" + atomicInt.get());
atomicInt.compareAndSet(101, 100);
log("thread intT1:" + atomicInt.get());
}
});
Thread intT2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean c3 = atomicInt.compareAndSet(100, 101);
log("thread intT2:" + atomicInt.get() + ",c3 is:" + c3); //true
}
});
intT1.start();
intT2.start();
上面程序的打印結(jié)果如下:
thread intT1:101
thread intT1:100
thread intT2:101,c3 is:true
線程intT2獲取到的變量值A(chǔ)载荔,盡管和當(dāng)前的實(shí)際值相同盾饮,但內(nèi)存地址V中的變量已經(jīng)經(jīng)歷了A->B->A的改變。實(shí)際應(yīng)用中有可能會(huì)導(dǎo)致漫畫(huà):什么是CAS機(jī)制懒熙?(進(jìn)階篇)中所提到的提款問(wèn)題丘损。
解決方案
JDK的atomic包里提供了一個(gè)類(lèi)AtomicStampedReference來(lái)解決ABA問(wèn)題。如果當(dāng)前引用 == 預(yù)期引用工扎,并且當(dāng)前標(biāo)志等于預(yù)期標(biāo)志徘钥,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。源碼如下:
/**
*expectedReference - 該引用的預(yù)期值
*newReference - 該引用的新值
*expectedStamp - 該標(biāo)志的預(yù)期值
*newStamp - 該標(biāo)志的新值
*/
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) ||
casPair(current, Pair.of(newReference, newStamp)));
}
最佳實(shí)踐
private static AtomicStampedReference<Integer> atomicStampedRef =
new AtomicStampedReference<Integer>(100, 0);
Thread refT1 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedRef.compareAndSet(100, 101,
atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
log("thread refT1:" + atomicStampedRef.getReference());
atomicStampedRef.compareAndSet(101, 100,
atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
log("thread refT1:" + atomicStampedRef.getReference());
}
});
Thread refT2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = atomicStampedRef.getStamp();
log("before sleep : stamp = " + stamp); // stamp = 0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
log("thread refT2:" + atomicStampedRef.getReference() + ",c3 is " + c3); //true
}
});
refT1.start();
refT2.start();
}
private static void log(String logString) {
System.out.println(logString);
}
輸出結(jié)果如下:
before sleep : stamp = 0
thread refT1:101
thread refT1:100
after sleep : stamp = 2
thread refT2:100,c3 is false
可以看到refT2的值和expect是相同的肢娘,但是由于版本號(hào)發(fā)生了變化呈础,所以更新失敗舆驶。
漫畫(huà):什么是 CAS 機(jī)制?
漫畫(huà):什么是CAS機(jī)制而钞?(進(jìn)階篇)
Java CAS 和ABA問(wèn)題