無鎖的同步策略——CAS操作詳解
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
/**
* 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操作詳解