什么是CAS
是一種思想,是一種實現(xiàn)線程安全的算法莺匠,同時也是一條CPU指令,比如Compare and Swap這一條指令就能完成“比較并交換”原子操作。
CAS有三個操作數(shù):內(nèi)存值V阳掐、預期值A、要修改的值B冷蚂,當且晉檔預期值A和內(nèi)存值V相同時缭保,才將內(nèi)存值修改為B,否則什么都不做蝙茶。最后返回現(xiàn)在的V值艺骂。
用大白話說就是:我任務V的值應該是A,如果是的話那我就把它改成B隆夯,如果不是A(說明被別人修改過了)钳恕,那我就不修改了。
這種思想最終通過調(diào)用CPU的一些特殊指令來實現(xiàn)蹄衷,這些指令由CPU保證了“比較和交換”兩個動作的原子性苞尝。
分析在java中是如何利用CAS的CPU指令實現(xiàn)“比較和交換”原子操作
讓我們來看看AtomicInteger是如何通過CAS實現(xiàn)并發(fā)下的累加操作的,以AtomicInteger的getAndAdd方法為突破口宦芦。
getAndAdd方法
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
可以看出宙址,這里使用了Unsafe這個類,這里需要簡要介紹一下Unsafe類:
Unsafe類
Unsafe是CAS的核心類调卑。Java無法直接訪問底層操作系統(tǒng)抡砂,而是通過本地(native)方法來訪問。不過盡管如此恬涧,JVM還是開了一個后門注益,JDK中有一個類Unsafe,它提供了硬件級別的原子操作溯捆。
AtomicInteger加載Unsafe工具丑搔,用來直接操作內(nèi)存數(shù)據(jù)
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public final int get() {
return value;
}
在AtomicInteger數(shù)據(jù)定義的部分厦瓢,我們還獲取了unsafe實例,并且定義了valueOffset啤月。再看到static塊煮仇,懂類加載過程的都知道,static塊的加載發(fā)生于類加載的時候谎仲,是最先初始化的浙垫,這時候我們調(diào)用unsafe的objectFieldOffset從Atomic類文件中獲取value的偏移量,那么valueOffset其實就是記錄value值在內(nèi)存中的偏移地址郑诺。
有了value在工作內(nèi)存中的偏移地址之后夹姥,我們就可以用unsafe直接操作這個地址了,通過這個地址我們可以獲取原值辙诞,也可以寫入新值辙售。
因為是修改工作內(nèi)存中的value值,所以value是用volatile修飾的飞涂,保證了多線程之間不會出現(xiàn)可見性的問題旦部。
接下來繼續(xù)看Unsafe的getAndAddInt方法的實現(xiàn)
// AtomicInter類
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// Unsafe類
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
我們看var5獲取的是什么,通過調(diào)用unsafe的getIntVolatile(var1, var2)封拧,這是個native方法志鹃,其實就是獲取var1中,var2偏移量處的值泽西。var1就是AtomicInteger曹铃,var2就是我們前面提到的valueOffset,這樣我們就從內(nèi)存里獲取到現(xiàn)在valueOffset處的值了。
現(xiàn)在重點來了捧杉,compareAndSwapInt(var1, var2, var5, var5 + var4)其實換成compareAndSwapInt(obj, offset, expect, update)比較清楚陕见,意思就是如果obj內(nèi)的value和expect相等,就證明沒有其他線程改變過這個變量味抖,那么就更新它為update评甜,如果這一步的CAS沒有成功,那就采用自旋的方式繼續(xù)進行CAS操作仔涩。
加法意味著只要在最新的值上加上我要加的值即可忍坷,我先取出工作內(nèi)存中最新的值(有volatile保證最新),然后進行“比對并交換”原子操作熔脂。比對成功說明我剛從工作內(nèi)存中取出的值是最新的佩研,可以往該值加上我要加的值。比對不成功霞揉,說明我剛剛取出工作內(nèi)存中的值的確是最新的旬薯,但不幸過后就被別的線程修改了,我比對的這一刻已經(jīng)不是最新的了适秩,所以我不能往該值加上我要加的值(加的話會導致加少)绊序,只能再次去工作內(nèi)存中獲取最新的值再加上硕舆,不釋放CPU,也就是在自旋骤公。
-->自旋+cas = atomicInteger
Unsafe類中的compareAndSwapInt本地方法
根據(jù)偏移量拿到value值在工作內(nèi)存中的地址
通過Atomic:cmpxchg實現(xiàn)原子性的比較和替換抚官,其中x是即將更新的值,e是原內(nèi)存的值淋样。
CAS思想在別的地方的體現(xiàn)
- 在數(shù)據(jù)庫更新操作中耗式,更新語句的where條件會帶上一個版本號胁住,若版本號不符合趁猴,則不進行更新操作。隨后再select出最新的版本號彪见,再嘗試去進行更新操作儡司。如此反復。
缺點
- ABA問題
可以通過版本號來解決 - 競爭激烈時余指,自旋時間可能會過長