1. 程式人生 > >獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

本文標題:最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖 
轉載請保留頁面地址:http://youzhixueyuan.com/detailed-explanation-of-java-lock.html

在Java併發場景中,會涉及到各種各樣的鎖如公平鎖,樂觀鎖,悲觀鎖等等,這篇文章介紹各種鎖的分類:

公平鎖/非公平鎖

可重入鎖

獨享鎖/共享鎖

樂觀鎖/悲觀鎖

分段鎖

自旋鎖

最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

樂觀鎖 VS 悲觀鎖

樂觀鎖與悲觀鎖是一種廣義上的概念,體現了看待執行緒同步的不同角度,在Java和資料庫中都有此概念對應的實際應用。

1.樂觀鎖

顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。

樂觀鎖適用於多讀的應用型別,樂觀鎖在Java中是通過使用無鎖程式設計來實現,最常採用的是CAS演算法,Java原子類中的遞增操作就通過CAS自旋實現的。

CAS全稱 Compare And Swap(比較與交換),是一種無鎖演算法。在不使用鎖(沒有執行緒被阻塞)的情況下實現多執行緒之間的變數同步。java.util.concurrent包中的原子類就是通過CAS來實現了樂觀鎖。

簡單來說,CAS演算法有3個三個運算元:

  •  需要讀寫的記憶體值 V。
  •  進行比較的值 A。
  •  要寫入的新值 B。

當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則返回V。這是一種樂觀鎖的思路,它相信在它修改之前,沒有其它執行緒去修改它;而Synchronized是一種悲觀鎖,它認為在它修改之前,一定會有其它執行緒去修改它,悲觀鎖效率很低

2.悲觀鎖

總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。

傳統的MySQL關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。詳情可以參考:

阿里P8架構師談:MySQL行鎖、表鎖、悲觀鎖、樂觀鎖的特點與應用

再比如上面提到的Java的同步synchronized關鍵字的實現就是典型的悲觀鎖。

最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

3.總之:

  •  悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時資料正確。
  •  樂觀鎖適合讀操作多的場景,不加鎖的特點能夠使其讀操作的效能大幅提升。

公平鎖 VS 非公平鎖

1.公平鎖

就是很公平,在併發環境中,每個執行緒在獲取鎖時會先檢視此鎖維護的等待佇列,如果為空,或者當前執行緒是等待佇列的第一個,就佔有鎖,否則就會加入到等待佇列中,以後會按照FIFO的規則從佇列中取到自己。

公平鎖的優點是等待鎖的執行緒不會餓死。缺點是整體吞吐效率相對非公平鎖要低,等待佇列中除第一個執行緒以外的所有執行緒都會阻塞,CPU喚醒阻塞執行緒的開銷比非公平鎖大。

2.非公平鎖

上來就直接嘗試佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。

非公平鎖的優點是可以減少喚起執行緒的開銷,整體的吞吐效率高,因為執行緒有機率不阻塞直接獲得鎖,CPU不必喚醒所有執行緒。缺點是處於等待佇列中的執行緒可能會餓死,或者等很久才會獲得鎖。

最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

3.典型應用:

java jdk併發包中的ReentrantLock可以指定建構函式的boolean型別來建立公平鎖和非公平鎖(預設),比如:公平鎖可以使用new ReentrantLock(true)實現。

獨享鎖 VS 共享鎖

1.獨享鎖

是指該鎖一次只能被一個執行緒所持有。

2.共享鎖

是指該鎖可被多個執行緒所持有。

3.比較

對於Java ReentrantLock而言,其是獨享鎖。但是對於Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。

讀鎖的共享鎖可保證併發讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

4.AQS

抽象佇列同步器(AbstractQueuedSynchronizer,簡稱AQS)是用來構建鎖或者其他同步元件的基礎框架,它使用一個整型的volatile變數(命名為state)來維護同步狀態,通過內建的FIFO佇列來完成資源獲取執行緒的排隊工作。

最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖

concurrent包的實現結構如上圖所示,AQS、非阻塞資料結構和原子變數類等基礎類都是基於volatile變數的讀/寫和CAS實現,而像Lock、同步器、阻塞佇列、Executor和併發容器等高層類又是基於基礎類實現。

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry陣列,陣列中的每個元素又是一個連結串列;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道他要放在那一個分段中,然後對這個分段進行加鎖,所以當多執行緒put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。

但是,在統計size的時候,可就是獲取hashmap全域性資訊的時候,就需要獲取所有的分段鎖才能統計。

分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個陣列的時候,就僅僅針對陣列中的一項進行加鎖操作。