1. 程式人生 > >CAS無鎖演算法:ReentrantLock,synchronized(JDK 1.6),悲觀鎖/樂觀鎖

CAS無鎖演算法:ReentrantLock,synchronized(JDK 1.6),悲觀鎖/樂觀鎖

> 悲觀鎖/樂觀鎖

 悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如Java裡面的同步原語synchronized關鍵字的實現也是悲觀鎖。synchronized是悲觀鎖,這種執行緒一旦得到鎖,其他需要鎖的執行緒就掛起的情況就是悲觀鎖。
 樂觀鎖:顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變數類就是使用了樂觀鎖的一種實現方式CAS實現的。樂觀鎖的一種實現方式-CAS(Compare and Swap 比較並交換).CAS操作的就是樂觀鎖,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

> Java1.6為Synchronized做了優化

儘管Java1.6為Synchronized做了優化,增加了從偏向鎖到輕量級鎖再到重量級鎖的過度,但是在最終轉變為重量級鎖之後,效能仍然較低。

 - jdk1.6以後 對synchronized鎖做了哪些優化:
 1.適應自旋鎖,自旋鎖:為了減少執行緒狀態改變帶來的消耗 不停地執行當前執行緒 
 2.鎖消除:不可能存在共享資料競爭的鎖進行消除
 3.鎖粗化:將連續的加鎖 精簡到只加一次鎖
 4.輕量級鎖:無競爭條件下 通過CAS消除同步互斥
 5.偏向鎖:無競爭條件下 消除整個同步互斥,連CAS都不操作。

> CAS

  java.util.concurrent(簡稱JUC)包,ReentrantLock也是基於CAS的。
  CAS(比較與交換,Compare and swap) 是一種有名的無鎖演算法。CAS(比較與交換,Compare and swap) 是一種有名的無鎖演算法。無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱為“無鎖程式設計演算法”( Non-blocking algorithm)。相對應的,獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖。
 
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);  
}
  從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。Atomic操作的底層實現正是利用的CAS機制.
 無鎖演算法——CAS原理- https://blog.csdn.net/roy_70/article/details/69799845

  與鎖相比,volatile變數是一和更輕量級的同步機制,因為在使用這些變數時不會發生上下文切換和執行緒排程等操作,但是volatile變數也存在一些侷限:不能用於構建原子的複合操作,因此當一個變數依賴舊值時就不能使用volatile變數。
  Java中的原子操作( atomic operations),原子操作指的是在一步之內就完成而且不能被中斷。原子操作在多執行緒環境中是執行緒安全的,無需考慮同步的問題。那麼long型賦值不是原子操作呢?實時上java會分兩步寫入這個long變數,先寫32位,再寫後32位。這樣就執行緒不安全了。如果改成下面的就執行緒安全了:private volatile long foo; 因為volatile內部已經做了synchronized.
  實現無鎖(lock-free)的非阻塞演算法有多種實現方法,其中 CAS(比較與交換,Compare and swap) 是一種有名的無鎖演算法。CAS的語義是“我認為V的值應該為A,如果是,那麼將V的值更新為B,否則不修改並告訴V的值實際為多少”,CAS是一種 樂觀鎖 技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。
   CAS看起來很爽,但是會導致“ABA問題”。CAS演算法實現一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較並替換,那麼在這個時間差類會導致資料的變化。比如說一個執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,並且two進行了一些操作變成了B,然後two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後one操作成功。儘管執行緒one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果連結串列的頭在變化了兩次後恢復了原值,但是不代表連結串列就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進行原子操作。
  CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

  優化鎖實現的例子 :Java中的ConcurrentHashMap,設計巧妙,用桶粒度的鎖和鎖分離機制,避免了put和get中對整個map的鎖定,尤其在get中,只對一個HashEntry做鎖定操作,效能提升是顯而易見的。
  Lock-free無鎖的例子 :CAS(Compare-And-Swap)的利用和LMAX的disruptor 無鎖訊息佇列資料結構等。例如ConcurrentLinkedQueue。

 輕量級鎖和偏向鎖都是在沒有競爭的情況下出現,一旦出現競爭就會升級為重量級鎖。對於synchronized,鎖的升級情況可能是 偏向鎖—>輕量鎖—>自適應自旋鎖—>重量鎖。

由於java的CAS同時具有 volatile 讀和volatile寫的記憶體語義,因此Java執行緒之間的通訊現在有了下面四種方式:
 1.A執行緒寫volatile變數,隨後B執行緒讀這個volatile變數。
 2.A執行緒寫volatile變數,隨後B執行緒用CAS更新這個volatile變數。
 3.A執行緒用CAS更新一個volatile變數,隨後B執行緒用CAS更新這個volatile變數。
 4.A執行緒用CAS更新一個volatile變數,隨後B執行緒讀這個volatile變數。

  AQS,非阻塞資料結構和原子變數類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴於這些基礎類來實現的。

關於Java鎖機制- https://yq.aliyun.com/articles/607025?utm_content=m_1000005240#