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

樂觀鎖 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全局信息的時候,就需要獲取所有的分段鎖才能統計。

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

原文鏈接:http://youzhixueyuan.com/detailed-explanation-of-java-lock.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章