CAS原子操作以及其在Java中的應用

zhong0316發表於2019-02-24

CAS(Compare And Swap)意為比較並且交換,CAS它是一個原子操作。CAS操作涉及到三個值:當前記憶體中的值V,逾期記憶體中的值E和待更新的值U。如果當前記憶體中的值V等於預期值E,則將記憶體中的值更新為U,CAS操作成功。否則不更新CAS操作失敗。 CAS在JUC中有廣泛的運用,可以說CAS是JUC的基礎,沒有CAS操作就沒有JUC。CAS經常用來實現Java中的樂觀鎖,相對於Java中的悲觀鎖synchronized鎖,樂觀鎖不需要掛起和喚醒執行緒,在高併發情況下,執行緒頻繁掛起和喚醒會影響效能。為了弄清CAS操作,有必要先了解一下樂觀鎖和悲觀鎖以及它們之間的區別。

悲觀鎖和樂觀鎖

悲觀鎖認為其他執行緒的每一次操作都會更新共享變數,因此所有的操作必須互斥,通過悲觀鎖策略來讓所有的操作序列化,所有的執行緒操作共享變數之前必須獲取悲觀鎖,獲取成功則進行操作,獲取失敗就阻塞當前執行緒悲觀等待。當執行緒被阻塞住之後CPU將不再排程執行緒,在高併發的場景下如果執行緒激烈競爭某一個鎖造成執行緒頻繁掛起和喚醒,無疑將給我們的應用帶來災難性的打擊。悲觀鎖的流程圖如下:

悲觀鎖

Java中的synchronized鎖和ReentrantLock都是悲觀的鎖,在前面的文章中分析了Java中各種鎖的區別,有興趣的可以前去檢視:多執行緒安全性和Java中的鎖

樂觀鎖相對悲觀鎖來說則是認為其他執行緒不一定會修改共享變數,因此執行緒不必阻塞等待。通過CAS來更新共享變數,如果CAS更新失敗則證明其他執行緒修改了這個共享變數,自己迴圈重試直到更新成功就可以了。因為CAS操作不會掛起執行緒因此減少了執行緒掛起和喚醒的開銷,在高併發情況下這個節省是非常可觀的。迴圈重試雖然不會掛起執行緒但是會消耗CPU,因為執行緒需要一直迴圈重試,這也是CAS樂觀鎖的一個缺點。java.util.concurrent.atomic包下面的Atomic類都是通過CAS樂觀鎖來保證執行緒安全性的。CAS樂觀鎖還有另一個缺點就是無法解決“ABA”問題。這個後面會進行詳細的分析。 基於CAS樂觀鎖流程圖如下所示:

基於CAS的樂觀鎖

CAS操作以及在Java中的應用

CAS在Java中有很多應用,JUC以及java.util.concurrent.atomic下面的原子類都用到了CAS。

Unsafe提供的CAS操作

Unsafe為Java提供了很多底層功能,其中Java中的CAS功能就是通過這個類來實現的。有興趣的可以檢視我前面專門講解Unsafe這個類的文章:Java中的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);
複製程式碼

Unsafe中提供了Object,int和long型別的CAS操作。其他型別的需要自己實現。CAS它是一個原子操作。要保證執行緒安全性,除了原子性,還有可見性和有序性。可見性和有序性在Java中都可以通過volatile來實現。

Java中的原子類(java.util.concurrent.atomic

java.util.concurrent.atomic包中的類通過volatile+CAS重試保證執行緒安全性。 java.util.concurrent.atomic包下面的原子類可以分為四種型別:

  1. 原子標量:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  2. 陣列類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  3. 更新類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  4. 複合變數類:AtomicMarkableReference,AtomicStampedReference

原子標量類

我們主要分析一下AtomicInteger這個類如何保證執行緒安全性:

AtomicInteger的value是volatile的

public class AtomicInteger extends Number implements java.io.Seriablizable {
    ...
    private volatile int value; // value是volatile的,保證了可見性和有序性
    ...
}
複製程式碼

AtomicInteger中的value是volatile的,volatile可以保證可見性和有序性。

get操作

public final int get() {
    return value;
}
複製程式碼

可以看到AtomicInteger的get操作是不加鎖的,對於非volatile型別的共享變數,併發操作時,一個讀執行緒未必能立馬讀取到其他執行緒對這個共享變數的修改。但是這裡的value是volatile的,因此可以立馬看到其他執行緒對value的修改。

incrementAndGet操作

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
複製程式碼

incrementAndGet操作會先將value加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;
}
複製程式碼

可以看到Unsafe中在迴圈體內先讀取記憶體中的value值,然後CAS更新,如果CAS更新成功則退出,如果更新失敗,則迴圈重試直到更新成功。

在前面的文章中(Java中的Unsafe)我們分析了Unsafe中提供了三種型別物件的CAS操作:Object,int和long型別。AtomicLong是通過Unsafe提供的long型別的CAS操作實現的,AtomicReference是通過Unsafe提供的Object型別的CAS操作實現的,而AtomicBoolean中的value也是一個int型別,AtomicBoolean對int做了一個轉換:

public AtomicBoolean(boolean initialValue) {
    value = initialValue ? 1 : 0;
}
複製程式碼

1表示true,0表示false。因此AtomicBoolean也是通過Unsafe提供的int型別的CAS操作來實現的。

陣列類

Unsafe提供了陣列相關的兩個主要功能:

public native int arrayBaseOffset(Class<?> var1);

public native int arrayIndexScale(Class<?> var1);
複製程式碼

AtomicIntegerArray主要就是使用這兩個方法來實現陣列CAS操作。AtomicIntegerArray中主要的一些功能如下:

private static final Unsafe unsafe = Unsafe.getUnsafe();  
private static final int base = unsafe.arrayBaseOffset(int[].class);  // 獲取陣列中第一個元素實際地址相對整個陣列物件的地址的偏移量
private static final int scale = unsafe.arrayIndexScale(int[].class); // 獲取陣列中第一個元素所佔用的記憶體空間
private final int[] array;  
public final int get(int i) {  
    return unsafe.getIntVolatile(array, rawIndex(i));  
}  
public final void set(int i, int newValue) {  
    unsafe.putIntVolatile(array, rawIndex(i), newValue);  
}
複製程式碼

AtomicLongArray和AtomicReferenceArray實現原理和AtomicIntegerArray類似。

更新類

AtomicLongFieldUpdater用來更新一個物件的 volatile long 型別的屬性,這個屬性是例項的屬性而不是類的屬性,也就是說只能用來更新一個物件的例項的非static屬性。 與AtomicLongFieldUpdater類似AtomicIntegerFieldUpdater用來更新一個物件的 volatile int 型別的屬性。

AtomicLongFieldUpdater和AtomicIntegerFieldUpdater都是用來更新一個物件的原生屬性(int long),而AtomicReferenceFieldUpdater用來更新一個物件的包裝型別屬性。

複合變數類

基於CAS樂觀鎖無法解決“ABA”問題。解決“ABA”問題的主要思路就是給value打戳,AtomicStampedReference就是通過對值加一個戳(stamp)來解決“ABA”問題的。

public class AtomicStampedReference<V> {

    private static class Pair<T> {
        final T reference; // 值
        final int stamp; // 值的戳
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair; // volatile保證可見性和有序性
    ...
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
    ...
    private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); // 通過Unsafe的compareAndSwapObject來更新
    }
    ...
}
複製程式碼

總結

  1. java.util.concurrent.atomic通過基於CAS的樂觀鎖保證執行緒安全性。在多讀少寫的場景下,較synchronized鎖和ReentrantLock的悲觀鎖效能會更好。
  2. JUC中大量執行了Unsafe的CAS操作,Unsafe的CAS是JUC的基礎。
  3. 基於CAS的樂觀鎖無法解決“ABA”問題,AtomicStampedReference通過加戳來解決“ABA”問題。
  4. 基於CAS+迴圈的樂觀鎖會大量消耗CPU。

相關文章