1. 程式人生 > 實用技巧 >深入分析CAS

深入分析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 = new
AtomicInteger(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 方法的流程:假設有以下情景