cas無鎖化演算法

bluesmi發表於2018-12-11

無鎖演算法

       CAS, CPU指令,在大多數處理器架構,包括IA32、Space中採用的都是CAS指令,CAS的語義是“我認為V的值應該為A,如果是,那麼將V的值更新為B,否則不修改並告訴V的值實際為多少”,CAS是項 樂觀鎖 技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

      無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步, 所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱為“無鎖程式設計演算法”( Non-blocking algorithm)。

      相對應的,獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它執行緒不會造成干擾的情況下執行,會導致其它所有需要鎖的執行緒掛起, 等待持有鎖的執行緒釋放鎖。

  • 使用lock實現執行緒同步有很多缺點:
    • 產生競爭時,執行緒被阻塞等待,無法做到執行緒實時響應。
    • dead lock,死鎖。
    • live lock。
    • 優先順序翻轉。
    • 使用不當,造成效能下降。 當然在部分情況下,目前來看,無鎖程式設計並不能替代 lock。

實現級別

非同步阻塞的實現可以分成以下三個級別:

  • wait-free
    • 是最理想的模式,整個操作保證每個執行緒在有限步驟下完成。
    • 保證系統級吞吐(system-wide throughput)以及無執行緒飢餓。
    • 截止2011年,沒有多少具體的實現。即使實現了,也需要依賴於具體CPU。
  • lock-free
    • 允許個別執行緒飢餓,但保證系統級吞吐。確保至少有一個執行緒能夠繼續執行。
    • wait-free的演算法必定也是lock-free的。
  • obstruction-free
    • 在任何時間點,一個執行緒被隔離為一個事務進行執行(其他執行緒suspended),並且在有限步驟內完成。
    • 在執行過程中,一旦發現資料被修改(採用時間戳、版本號),則回滾。也叫做樂觀鎖,即樂觀併發控制(OOC)。
    • 事務的過程是:
      • 讀取,並寫時間戳;
      • 準備寫入,版本校驗;
      • 校驗通過則寫入,校驗不通過,則回滾。
    • lock-free必定是obstruction-free的。

底層介紹

        java.util.concurrent.atomic中的AtomicXXX,都使用了這些底層的JVM支援為數
字型別的引用型別提供一種高效的CAS操作,而在java.util.concurrent中的大多數類在實現時都直接或間接的使用了這些原子變數類,這些原子變數都呼叫了 sun.misc.Unsafe 類庫裡面的 CAS演算法,用CPU指令來實現無鎖自增.

//JDK原始碼:
public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
}

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
複製程式碼

      因而在大部分情況下,java中使用Atomic包中的incrementAndGet的效能比用synchronized高出幾倍。

ABA問題

  • 問題描述       thread1意圖對val=1進行操作變成2,cas(val,1,2)。       thread1先讀取val=1;thread1被搶佔(preempted),讓thread2執行。       thread2 修改val=3,又修改回1。       thread1繼續執行,發現期望值與“原值”(其實被修改過了)相同,完成CAS操作。

  • 解決方案

    • ABA:新增額外的標記用來指示是否被修改。 從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

相關文章