CAS全稱 Compare And Swap(比較與交換),在不使用鎖的情況下實作多線程之間的變量同步。屬于硬體同步原語,處理器提供了基本記憶體操作的原子性保證。juc包中的原子類就是通過CAS來實作了樂觀鎖。
CAS算法涉及到三個操作數:
- 需要讀寫的記憶體值 V。
- 進行比較的舊值A (期望操作前的值)
- 要寫入的新值 B。
當且僅當 V 的值等于 A 時,CAS通過原子方式用新值B來更新V的值(“比較+更新”整體是一個原子操作),否則不會執行任何操作。 一般情況下,“更新”是一個不斷重試的過程。
JAVA中的sun.misc.Unsafe類,提供了
- compareAndSwapInt
- compareAndSwapLong
等方法實作CAS。
- 示例

J.U.C包内的原子操作封裝類
看一下AtomicInteger的源碼定義:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
複制
各屬性的作用:
- unsafe: 擷取并操作記憶體的資料
- valueOffset: 存儲value在AtomicInteger中的偏移
- value: 存儲AtomicInteger的int值,該屬性需要借助volatile關鍵字保證其線上程間的可見性
接着檢視自增方法
incrementAndGet
的源碼時,發現自增函數底層調用的是
unsafe.getAndAddInt
。 但是由于JDK本身隻有Unsafe.class,隻通過class檔案中的參數名,并不能很好的了解方法的作用,是以我們通過OpenJDK 8 來檢視Unsafe的源碼:
// ------------------------- JDK 8 -------------------------
// AtomicInteger 的自增方法
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe.class
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));
return var5;
}
// ------------------------- OpenJDK 8 -------------------------
// Unsafe.java
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
複制
由源碼可看出,getAndAddInt()循環擷取給定對象o中的偏移量處的值v,然後判斷記憶體值是否等于v。
- 如果相等則将記憶體值設定為 v + delta
- 否則傳回false,繼續循環進行重試,直到設定成功才能退出循環,并且将舊值傳回
整個“比較+更新”操作封裝在compareAndSwapInt()中,通過JNI使用CPU指令完成的,屬于原子操作,可以保證多個線程都能夠看到同一個變量的修改值。
JDK通過CPU的cmpxchg指令,去比較寄存器中的 A 和 記憶體中的值 V。如果相等,就把要寫入的新值 B 存入記憶體中。如果不相等,就将記憶體值 V 指派給寄存器中的值 A。然後通過Java代碼中的while循環再次調用cmpxchg指令進行重試,直到設定成功為止。
CAS的問題
循環+CAS
自旋的實作讓所有線程都處于高頻運作,争搶CPU執行時間的狀态。CAS操作如果長時間不成功,會導緻其一直自旋,如果操作長時間不成功,會帶來很大的CPU資源消耗。
隻能保證一個共享變量的原子操作
對一個共享變量執行操作時,CAS能夠保證原子操作,但是對多個共享變量操作時,CAS是無法保證操作的原子性的。 Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個變量放在一個對象裡來進行CAS操作。
ABA問題(無法展現資料的變動)
CAS需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是A,後來變成了B,然後又變成了A,那麼CAS進行檢查時會發現值沒有發生變化,但是實際上是有變化的。 ABA問題的解決思路就是在變量前面添加版本号,每次變量更新的時候都把版本号加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。
JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,具體操作封裝在compareAndSet()中。 compareAndSet()首先檢查目前引用和目前标志與預期引用和預期标志是否相等,如果都相等,則以原子方式将引用值和标志的值設定為給定的更新值。 不過目前來說這個類比較”雞肋”,大部分情況下 ABA 問題并不會影響程式并發的正确性,如果需要解決 ABA 問題,使用傳統的互斥同步可能比原子類更加高效。
隻能保證一個共享變量的原子操作。對一個共享變量執行操作時,CAS能夠保證原子操作,但是對多個共享變量操作時,CAS是無法保證操作的原子性的。 Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個變量放在一個對象裡來進行CAS操作。
Java 8 更新
當然這都是由 Doug Lea 大佬親自為 Java 操刀
更新器
DoubleAccumulator
LongAccumulator
計數器
DoubleAdder
LongAdder
計數器增強版,高并發下性能更好 頻繁更新但不太頻繁讀取的彙總統計資訊時使用分成多個操作單元,不同線程更新不同的單元
隻有需要彙總的時候才計算所有單元的操作
T1執行後,A 變成了B
T3又開始執行了, B變成了A
T2開始執行, A變成了C
- 問題點:
- 經曆的A -> B -> A過程,但是對于線程2,無法感覺資料發生了變化