CAS

文戲那瞥發表於2019-01-19

CAS介紹

CAS – Compare And Swap (Compare And Set, Check And Set)

wikipedia的描述如下:

比較並交換(compare and swap, CAS),是原子操作的一種,可用於在多執行緒程式設計中實現不被打斷的資料交換操作,從而避免多執行緒同時改寫某一資料時由於執行順序不確定性以及中斷的不可預知性產生的資料不一致問題。 該操作通過將記憶體中的值與指定資料進行比較,當數值一樣時將記憶體中的資料替換為新的值。

Java在sun.misc.Unsafe類庫裡面的CAS實現。

以下原始碼摘自java.util.concurrent.locks.AbstractQueuedSynchronizer

/**
 * Atomically sets synchronization state to the given updated
 * value if the current state value equals the expected value.
 * This operation has memory semantics of a {@code volatile} read
 * and write.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that the actual
 *         value was not equal to the expected value.
 */
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    //this: 當前物件
    //stateOffSet: 偏移量,宣告在下面已貼出
    //expect: 期待值
    //update: 更新值
    //如果stateOffSet的值與expect相等,則將stateOffset的值更新為update;並返回true。
    //否則不更新,並返回false。
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
    
    ......
    
private static final long stateOffset;

static {
    try {
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        
            ......

    } catch (Exception ex) { throw new Error(ex); }
}
    

CAS與同步方式

在多執行緒情況下,可以採用同步阻塞(悲觀鎖)或CAS(樂觀鎖)的方式實現業務,具體要看業務場景,如果重試的代價很小,那用CAS是合適的,但如果每次重試都需要花費大量的時間或資源,那應該採用同步方式。

以下是2種方式的簡單舉例:

class MyLock {

    private boolean locked = false;

    public synchronized boolean lock() {
        if(!locked) {
            locked = true;
            return true;
        }
        return false;
    }
}
public static class MyLock {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public boolean lock() {
        return locked.compareAndSet(false, true);
    }

}

CAS缺點 – ABA問題

程式P1讀取了一個數值A
P1被掛起(時間片耗盡、中斷等),程式P2開始執行
P2修改數值A為數值B,然後又修改回A
P1被喚醒,比較後發現數值A沒有變化,程式繼續執行。

解決思路:在每次更新的同時附上版本號,如:1A -> 2B -> 3A。JDK1.5開始新增的java.util.concurrent.atomic.AtomicStampedReference就是一種實現方式。

Redis中的CAS

Redis可以使用WATCH來實現對事務中鍵(可以是多個鍵)的監視,如果至少有一個鍵在EXEC執行前被改動,那麼整個事務都會被取消, EXEC返回nil-reply來表示事務已經失敗。

具體參見:Redis – 事務(transactions)

相關文章