JDK1.5中增加的一個最主要的支持是Atomic類,如AtomicInteger、AtomicLong等,這些類可幫助最大限度地減少在多線程中對於一些基本操作(例如,增加或減少多個線程之間共享的值)的複雜性,而這些類的實現都依賴於CAS(compare and swap)的算法。
一、CAS
1.CAS原理
CAS的全稱是Compare And Swap——比較交換,CAS中有三個核心參數:
- 主內存中存放的V值,所有線程共享。
- 線程上次從內存中讀取的V值A存放在線程的幀棧中,每個線程私有。
- 需要寫入內存中並改寫V值的B值。也就是線程對A值操作後放入到主存V中。
CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,並重新計算得到B,如果相同,則將B值賦給V。
CAS是一種樂觀鎖,當多個線程同時使用CAS操作一個變量時,只有一個會勝出,併成功更新,其餘均會失敗,但失敗的線程並不會被掛起,僅是被告知失敗,並且允許再次嘗試,直到成功爲止(JDK代碼中採用while循環不斷自旋)。由於無鎖,因此不可能出現死鎖的情況,也就是說無鎖操作天生免疫死鎖。
source: 全面瞭解Java中的CAS機制
2.CAS的優劣
cpu是時分複用的,也就是把cpu的時間片,分配給不同的thread/process輪流執行,時間片與時間片之間,需要進行cpu切換,也就是會發生進程的切換。如果採用悲觀鎖(如Sychronizied獨佔鎖),在進程掛起和恢復執行過程中存在着很大的開銷,同時當一個線程正在等待鎖時,它不能做任何事。用樂觀鎖在讀多寫少的場景下,性能就會比較優越。
CAS之不足:
循環時間長開銷很大:如果CAS失敗,會一直進行嘗試。如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。
只能保證一個共享變量的原子操作:當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。
ABA問題
如果一開始位置V得到的舊值是A,當進行賦值操作時再次讀取發現仍然是A,並不能說明變量沒有被其它線程改變過。有可能是其它線程將變量改爲了B,後來又改回了A。大部分情況下ABA問題不會影響程序併發的正確性,如果要解決ABA問題,可以用傳統的互斥同步。Java併發包爲了解決這個問題,提供了一個帶有標記的原子引用類AtomicStampedReference
,它可以通過控制變量值的版本來保證CAS的正確性。
source: 面試必問的CAS,你懂了嗎
二、Atomic原子包
原子更新基本類型主要包括3個類:
- AtomicBoolean:原子更新布爾類型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新長整型
這3個類的實現原理和使用方式幾乎是一樣的,提供了原子自增方法、原子自減方法以及原子賦值方法等。原子類的內部幾乎是基於Unsafe類中的CAS相關操作的方法實現的,這也同時證明其是基於無鎖實現的。Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因爲Java中CAS操作的執行依賴於Unsafe類的方法,注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務。
//JDK 1.7的源碼,由for的死循環實現,並且直接在AtomicInteger實現該方法,
//JDK1.8後,該方法實現已移動到Unsafe類中,直接調用getAndAddInt方法即可
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
source:JAVA中的CAS