1. 程式人生 > >無鎖的同步策略——CAS操作詳解

無鎖的同步策略——CAS操作詳解

更新 src 使用 cst 線程互斥 地方 int jdk 它的

1. 從樂觀鎖和悲觀鎖談起

樂觀鎖和悲觀鎖是兩種不同的解決並發問題的策略。悲觀鎖策略假定任何一次並發都會發生沖突,所以總是采用最嚴格的方式來進行並發控制。java中的獨占鎖(synchronized和重入鎖)就是典型悲觀鎖實現,它只允許線程互斥的訪問臨界區,也就是阻塞式的同步方式。而樂觀鎖策略假定大部分情況下並發沖突不會發生,采用的是一種更為寬松的方式來進行並發控制。比如我們馬上就要講的CAS操作。它允許多線程非阻塞式地對共享資源進行修改,但同一時刻只有一個線程能夠成功,其他線程被告知失敗但並不會掛起,而是重新嘗試。這是一種非阻塞式的同步方式。

2. CAS詳解

Java中的CAS操作依賴於底層CPU的CAS指令。

2.1 CAS指令

CAS,即Compare-and-Swap(比較和交換),從語義上它需要兩次操作,但只需要一條cpu指令就能完成,因而該操作具有原子性,像原子一樣不可分割,要麽成功,要麽失敗。
CAS指令需要3個操作數,分別是V:變量的內存地址,A(預期值),B(更新值)。CAS指令執行時,只有當預期值A和V的值一樣時才進行更新,否則更新失敗。

2.3 Java中的CAS指令

java中給我們提供了本地方法來獲得和CAS指令一樣的執行效果。比如Unsafe類中

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

因為這些方法會被編譯成平臺相關的CAS指令,故而這些CAS操作都具有原子性。遺憾的是,這些CAS操作我們無法直接使用,因為只有Bootstrap ClassLoader加載的Class才能訪問它。然而在JDK並發包的底層實現中,還是可以處處看到它的身影。如下圖所示
技術分享圖片

2.4 CAS結合失敗重試機制進行並發控制

CAS指令只是提供了一個更新變量的原子操作,要使用它進行並發控制,還需要結合失敗重試機制。以AtomicInteger為例,對它進行累加操作是線程安全的,而普通的整型變量在多線程環境下執行類似i++的操作線程不安全。為什麽?因為i++並非是一個原子操作,而AtomicInteger類的getAndIncrement

底層的CAS操作是原子性的,故而能保證線程安全。下面是它的源碼(基於JDK8)

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

進入方法Unsafe類的getAndAddInt方法,

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)); //CAS失敗則重新嘗試直到成功為止

    return var5;
}

可以看到步驟可以概括為

  • 1.獲得變量當前的值var5
  • 2.使用CAS操作進行更新:若var5與當前的值不一樣,說明1,2操作間有其他線程作了修改,此次更新失敗,重新實行步驟1;否則用 var5+var4的值進行更新,並返回更新前的值。


    多個線程同時使用CAS指令去更新變量,失敗的線程將會不斷重新嘗試,直到更新成功。

3. CAS操作的優勢和劣勢

3.1 CAS相比獨占鎖的優勢

  • 沒有線程阻塞喚醒帶來的性能消耗問題。

3.2 CAS的缺點

  • ABA問題。在CAS操作時,我們以變量的當前值和預期值一致來判定變量未被其他線程修改,這樣是不用嚴謹的,因為變量可能被修改成其他值後又被改了回來,大部分時候這是個可以忽略的小問題,如果要規避這個問題,可以使用AtomicStampedReference,它會額外使用一個時間戳來判斷變量是否被修改過。
  • 無法直接使用CAS來進行並發控制,相比同步鎖的方式適用範圍較窄。

4. 總結

CAS是CPU的一條指令,用來對變量進行原子更新。java中使用CAS技術結合失敗重試機制,可以非阻塞的實現多線程對共享資源的並發修改,很多時候具有比獨占鎖更好的性能。

無鎖的同步策略——CAS操作詳解