深入分析CAS
在 Java 併發中,我們最初接觸的應該就是synchronized關鍵字,但是synchronized屬於重量級鎖,很多時候會引起效能問題,volatile也是個不錯的選擇,但是volatile不能保證原子性,只能在某些場合下使用。
像synchronized這種獨佔鎖屬於悲觀鎖,它是在假設一定會發生衝突的,那麼加鎖恰好有用,除此之外,還有樂觀鎖,樂觀鎖的含義就是假設沒有發生衝突,那麼我正好可以進行某項操作,那麼發生衝突呢,那我就重試直到成功,樂觀鎖最常見的就是CAS。
1. 什麼是 CAS
① CAS(compare and swap)比較並交換,比較和替換是執行緒併發演算法時用到的一種技術
② CAS 是原子操作,保證併發安全,而不是保證併發同步
③ CAS 是 CPU 的一個指令
④ CAS 是非阻塞的、輕量級的樂觀鎖
2. 為什麼說 CAS 是樂觀鎖
樂觀鎖,嚴格來說並不是鎖,通過原子性來保證資料的同步,比如說資料庫的樂觀鎖,通過版本控制來實現等,所以 CAS 不會保證執行緒同步。
樂觀的認為在資料更新期間沒有其它執行緒影響。
3. CAS 原理
CAS(compare and swap)比較並替換,就是將記憶體值更新為需要的值,但是有個條件,記憶體值必須與期望值相同。
舉個例子:期望值 E 、記憶體值 M 、更新值 U ,當 E == M 的時候將 M 更新為 U 。
4. CAS 應用
由於 CAS 是 CPU 指令,我們只能通過 JNI 與作業系統互動,關於 CAS 的方法都在 sun.misc 包下 Unsafe 的類裡 java.util.current.atomic 包下的原子類等通過 CAS 來實現原子操作。
6. CAS 舉例
public class CasLock { private static CountDownLatch latch = new CountDownLatch(5); // 加法計數器 private static int num = 0; private static AtomicInteger atomicInteger = newAtomicInteger(0); public static void main(String[] args) throws InterruptedException { long time = System.currentTimeMillis(); for (int i = 0; i < 5; i++) { new Thread(() -> { for (int x = 0; x < 10000; x++) { num++; //不是原子操作 atomicInteger.getAndIncrement();//呼叫原子類加1 } latch.countDown(); // 執行緒執行完成,計數+1 }).start(); } latch.await();//保證所有子執行緒執行完成 System.out.println("耗時: " + (System.currentTimeMillis() - time) + " 毫秒"); System.out.println("num = " + num); System.out.println("atomicInteger = " + atomicInteger); } }
輸出結果:
耗時: 214 毫秒
num = 48450
atomicInteger = 50000
根據結果我們發現,由於多執行緒非同步進行 num++ 操作,導致結果不正確。
為什麼 num++ 的記過不正確呢?
比如:兩個執行緒讀到 num 的值為 1,然後做加 1 操作,這時候 num 的值是 2,而不是 3 而變數 atomicInteger 的結果卻是對的,這就要歸功於CAS,下面我們具體看一下原子類。
7. CAS指令和具體原始碼
原子類例如 AtomicInteger 裡的方法都很簡單,大家看一看都能懂,我們具體看下 getAndIncrement 方法。
//該方法功能是Interger型別加1 public final int getAndIncrement() { //主要看這個getAndAddInt方法 return unsafe.getAndAddInt(this, valueOffset, 1); } //var1 是this指標 //var2 是地址偏移量 //var4 是自增的數值,是自增1還是自增N public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //獲取記憶體值,這是記憶體值已經是舊的,假設我們稱作期望值E var5 = this.getIntVolatile(var1, var2); //compareAndSwapInt方法是重點, //var5是期望值,var5 + var4是要更新的值 //這個操作就是呼叫CAS的JNI,每個執行緒將自己記憶體裡的記憶體值M //與var5期望值E作比較,如果相同將記憶體值M更新為var5 + var4,否則做自旋操作 } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
getAndAddInt 方法的流程:假設有以下情景