07-J.U.C之CAS

1. 簡介

       CASCompare And Swap,即比較並交換。整個AQS同步組件、Atomic原子類操作等等都是以CAS實現的,甚至ConcurrentHashMap1.8的版本中也調整爲了CAS+Synchronized。可以說CAS是整個JUC的基石。

在這裏插入圖片描述

2. CAS 分析

       在CAS中有三個參數:內存值V、舊的預期值A、要更新的值B,當且僅當內存值V的值等於舊的預期值A時纔會將內存值V的值修改爲B,否則什麼都不幹。其僞代碼如下:

if(this.value == A){
    this.value = B
    return true;
}else{
    return false;
}

       JUC下的atomic類都是通過CAS來實現的,下面就以AtomicInteger爲例來闡述CAS的實現。如下:

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;

       UnsafeCAS的核心類,Java無法直接訪問底層操作系統,而是通過本地(native)方法來訪問。不過儘管如此,JVM還是開了一個後門:Unsafe,它提供了硬件級別的原子操作。valueOffset爲變量值在內存中的偏移地址,unsafe就是通過偏移地址來得到數據的原值的value當前值,使用volatile修飾,保證多線程環境下看見的是同一個。

       我們就以AtomicInteger的addAndGet()方法來做說明,先看源代碼:

public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

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;
}

       內部調用unsafegetAndAddInt方法,在getAndAddInt方法中主要是看compareAndSwapInt方法:

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

       該方法爲本地方法,有四個參數,分別代表:對象、對象的地址、預期值、修改值。

       CAS可以保證一次的讀-改-寫操作是原子操作,在單處理器上該操作容易實現,但是在多處理器上實現就有點兒複雜了。

       CPU提供了兩種方法來實現多處理器的原子操作:總線加鎖或者緩存加鎖。

       1、總線加鎖:總線加鎖就是就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔使用共享內存。但是這種處理方式顯得有點兒霸道,不厚道,他把CPU和內存之間的通信鎖住了,在鎖定期間,其他處理器都不能其他內存地址的數據,其開銷有點兒大。所以就有了緩存加鎖。

       2、緩存加鎖:其實針對於上面那種情況我們只需要保證在同一時刻對某個內存地址的操作是原子性的即可。緩存加鎖就是緩存在內存區域的數據如果在加鎖期間,當它執行鎖操作寫回內存時,處理器不在輸出LOCK#信號,而是修改內部的內存地址,利用緩存一致性協議來保證原子性。緩存一致性機制可以保證同一個內存區域的數據僅能被一個處理器修改,也就是說當CPU1修改緩存行中的i時使用緩存鎖定,那麼CPU2就不能同時緩存了i的緩存行。

3. CAS 缺陷

       CAS雖然高效地解決了原子操作,但是還是存在一些缺陷的,主要表現在三個方法:循環時間太長、只能保證一個共享變量原子操作、ABA問題。

       循環時間太長:如果CAS一直不成功呢?這種情況絕對有可能發生,如果自旋CAS長時間的不成功,則會給CPU帶來非常大的開銷。在JUC中有些地方就限制了CAS自旋的次數,例如BlockingQueue的SynchronousQueue

       只能保證一個共享變量原子操作:看了CAS的實現就知道這隻能針對一個共享變量,如果是多個共享變量就只能使用鎖了,當然如果你有辦法把多個變量整成一個變量,利用CAS也不錯。例如讀寫鎖中state的高低位

       ABA問題CAS需要檢查操作值有沒有發生改變,如果沒有發生改變則更新。但是存在這樣一種情況:如果一個值原來是A,變成了B,然後又變成了A,那麼在CAS檢查的時候會發現沒有改變,但是實質上它已經發生了改變,這就是所謂的ABA問題。對於ABA問題其解決方案是加上版本號,即在每個變量都加上一個版本號,每次改變時加1,即A —> B —> A,變成1A —> 2B —> 3A。

       CASABA隱患問題,解決方案則是版本號,Java提供了AtomicStampedReference來解決。AtomicStampedReference通過包裝[E,Integer]的元組來對對象標記版本戳stamp,從而避免ABA問題。

       AtomicStampedReferencecompareAndSet()方法定義如下:

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)));
}

       compareAndSet有四個參數,分別表示:預期引用、更新後的引用、預期標誌、更新後的標誌。源碼部分很好理解預期的引用 當前引用,預期的標識 當前標識,如果更新後的引用和標誌和當前的引用和標誌相等則直接返回true,否則通過Pair生成一個新的pair對象與當前pair CAS替換。

       PairAtomicStampedReference的內部類,主要用於記錄引用和版本戳信息(標識),定義如下:

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;

       Pair記錄着對象的引用和版本戳,版本戳爲int型,保持自增。同時Pair是一個不可變對象,其所有屬性全部定義爲final,對外提供一個of方法,該方法返回一個新建的Pari對象。pair對象定義爲volatile,保證多線程環境下的可見性。在AtomicStampedReference中,大多方法都是通過調用Pairof方法來產生一個新的Pair對象,然後賦值給變量pair。如set方法:

public void set(V newReference, int newStamp) {
    Pair<V> current = pair;
    if (newReference != current.reference || newStamp != current.stamp)
        this.pair = Pair.of(newReference, newStamp);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章