CAS 算法 —— Compare and Swap

本文翻譯和原創各佔一半,因此仍是厚顏無恥歸類到原創好了...
https://howtodoinjava.com/jav...
java 5 其中一個使人振奮的改進是新增了支持原子操做的類型,例如 AtomicInteger, AtomicLong 等。在多線程環境中進行簡單的自增自減操做時,這些原子類能幫助你減小不少用於多線程同步的複雜代碼。這些原子類依賴於 CAS (compare and swap) 算法,接下來咱們會討論 CAS 這個概念。java

樂觀鎖和悲觀鎖

傳統的鎖機制,例如 java 的 synchronized 關鍵字,他表明了 java 中悲觀鎖技術,保證了某一時刻僅有一個線程能訪問同步代碼/方法。synchronized 可以很好地工做,卻有着 (相對) 比較大的性能開銷。
樂觀鎖 (相對悲觀鎖) 對性能會有很大的幫助。他的核心思想是:你寄但願於在沒有衝突的狀況下完成一次更新操做,使用樂觀鎖技術更新時會進行 「衝突檢測」 來判斷是否有其餘的線程干擾,如果 (有其餘線程干擾) 則視本次更新操做失敗,通常會進行重試 (能夠了解一下CAS自旋)。Compare and Swap 就是典型的樂觀鎖技術。算法

CAS 算法

CAS 算法會先對一個內存變量(位置) V 和一個給定的值進行比較 A ,若是相等,則用一個新值 B 去修改這個內存變量(位置)。上述過程會做爲一個原子操做完成 (intel處理器經過 cmpxchg 指令系列實現)。CAS 原子性保證了新值的計算是基於上一個有效值,期間若是內存變量(位置) V 被其餘線程更新了,本線程的 CAS 更新操做將會失敗。CAS 操做必須告訴調用者成功與否,能夠返回一個 boolean 值來表示,或者返回一個從內存變量讀到的值 (應該是上一次有效值)多線程

CAS 操做數有三個:性能

  • 內存變量(位置) V,表示被更新的變量
  • 線程上一次讀到的舊值 A
  • 用來覆蓋 V 的新值 B
CAS 表示:「我認爲如今 V 的值仍是以前我讀到的舊值 A,如果則用新值 B 覆蓋內存變量 V,不然不作任何動做並告訴調用者操做失敗」。CAS 是一項樂觀鎖技術,他在更新的時候老是但願能成功 (沒有衝突),但也能檢測出來自其餘線程的衝突和干擾

Java 中的 Compare and Swap

這裏咱們關注一下ReentrantLock鎖定和解鎖那部分的源碼ui

//ReentrantLock.lock()
public void lock() {
    sync.lock();
}

他依賴了其內部類Synclock(),如下是內部類 Sync (繼承了隊列同步器 AQS)線程

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    abstract void lock();
    ................

Sync仍是個抽象類,通常 new ReentrantLock() 時建立的是 NonfairSync翻譯

// ReentrantLock的構造方法
public ReentrantLock() {
    sync = new NonfairSync();
}

下面就是NonfairSynclock() 方法了code

final void lock() {
    if (compareAndSetState(0, 1)) // 1
        setExclusiveOwnerThread(Thread.currentThread()); // 2
    else
        acquire(1); // 3
}
  • 1 中的 compareAndSetState() 承繼自隊列同步器 AQS,封裝了 CAS 指令。由於是 NonfairSync 非公平鎖,因此一上來就嘗試搶佔鎖:給定舊值 0 並但願用新值 1 去更新內存變量 State。若更新成功則視爲獲取鎖成功,並執行 2
  • 2 成功完成了 CAS 操做 (沒錯,當你使用 CAS 指令成功把 State 從 0 更新成 1 便視爲獲取鎖,就是這麼簡單粗暴 ╮(╯▽╰)╭ ),把當前線程設爲獨佔線程
  • 3 操做失敗 (被人搶先獲取鎖(╯`□′)╯╧╧),進行 acquire 操做再次嘗試獲取鎖,若仍是不行,則把當前線程加入 AQS 等待隊列,由 AQS 來管理隊列中等待線程的阻塞和喚醒,具體代碼就不貼出來了,AQS 的源碼多處使用到 CAS 指令,有興趣的同窗能夠查看

鎖用完了要釋放,下面貼出 unlock() 方法繼承

// ReentrantLock.unlock()
public void unlock() {
    sync.release(1);
}

這裏仍是依賴了 sync,release() 是 AQS 的通用方法,其內部調用了 tryRelease() (由 Sync 類實現),這裏直接貼出 Sync 的 tryRelease()隊列

protected final boolean tryRelease(int releases) { // releases 參數的值是上面傳進來的 1
    int c = getState() - releases; // 1
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 1.5
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // 2
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c); // 3
    return free;
}
  • 1 處的c 是內存變量 State 即將要被更新的值,由於 ReentrantLock 是可重入鎖 (當前線程可屢次獲取鎖),因此 State 的值是能夠大於 1 的。
  • 2 判斷若新值爲 0,則視爲鎖被釋放並設置當前獨佔線程爲 null
  • 3 把 State 的值更新爲 c,思考一下這裏的更新操做爲何沒用到 CAS 指令?
  • 1.5 解釋了上面的疑問,只有當前獨佔線程有能力對 State 變量進行修改,不須要進行同步或使用 CAS

Summary

AQS 隊列同步器以及 java.util.concurrent 下各類鎖和原子類都運用到的 CAS 算法,有時間的同窗建議閱讀加深印象。

相關文章
相關標籤/搜索