輕鬆掌握java讀寫鎖(ReentrantReadWriteLock)的實現原理
前言
前面介紹了java中排它鎖,共享鎖的底層實現機制,本篇再進一步,學習非常有用的讀寫鎖。鑑於讀寫鎖比其他的鎖要複雜,不想堆一大波的文字,本篇會試圖圖解式說明,把讀寫鎖的機制用另外一種方式闡述,鑑於本人水平有限,如果哪裡有誤,請不吝賜教。
公平讀寫鎖
ReentrantReadWriteLock的鎖策略有兩種,分為公平策略和非公平策略,兩者有些小區別,為便於理解,本小節將以示例的形式來說明多執行緒下,使用公平策略的讀寫鎖是如何處理的。
首先看一下即將出場的夥伴們,我們一共會出場幾個執行緒,還有用於實現讀寫機制的AQS同步器佇列。每個執行緒中的 R(0)W(0)表示當前執行緒佔用了多少讀寫鎖。
接下來,我們一步步來看在公平策略下多執行緒併發的讀寫機制是怎樣的。
1.執行緒A請求一個讀鎖,此時無人競爭鎖,A獲取讀鎖1,即執行緒A重入次數為1,如下所示:
2.執行緒B請求一個讀鎖,由於AQS中沒有等待節點,當前處於讀鎖佔有狀態(執行緒A佔有1個讀鎖),所以B成功獲取讀鎖,如下所示:
3.這時候,執行緒C請求一個寫鎖,由於當前其他兩個執行緒擁有讀鎖,寫鎖獲取失敗,執行緒C入佇列,如下所示:
AQS初始化會建立一個空的頭節點,C入佇列,然後會休眠,等待其他執行緒釋放鎖喚醒。
4.執行緒D也來了,執行緒D想獲取一個讀鎖,雖然當於處於讀鎖佔有階段,但是目前D不佔有任何數量的讀鎖,而且同步器佇列中已經有等待節點,這時候,由於公平策略,D不得已,一個字,等,如下圖所示:
5.這時候,執行緒A執行完了,釋放了讀鎖,由於B仍然佔有讀鎖,所以釋放後讀鎖仍然沒有完全釋放,寫鎖仍然沒有機會執行,如下圖所示:
6.這次,B也執行完了,執行完後,讀鎖全部釋放,這時候會喚醒排在同步器隊頭的節點C,C成功獲取一個寫鎖,如下圖所示:
7.一旦任何一個執行緒獲取了寫鎖,除了該執行緒自己,其它執行緒都將無法獲取讀鎖和寫鎖,這時候,執行緒C再次請求一個讀鎖,這是允許的,但反過來如果一個執行緒先獲取了讀鎖,再獲取寫法則是不行的。這時候的狀態如下圖所示:
8.這時候假設執行緒E也來了,E想獲取讀鎖,由於當前處於寫鎖狀態,直接入隊,如下所示:
9.這會C終於把活幹完了,把讀鎖和寫鎖都給釋放了,然後執行緒D被喚醒,獲取了讀鎖,如下圖所示:
10.這時候,如果再來一個執行緒,比如A,也想獲取讀鎖,由於節點中還有執行緒E在等待,而且當前執行緒A沒有獲取任何讀鎖,不是重入狀態,所以只能置入隊尾,如下圖所示:
11.這時候,如果D再次呼叫了一次獲取讀鎖,由於D屬於可重入狀態,所以直接把讀鎖+1即可,如下圖所示:
12.由於D獲取的是讀鎖,同步佇列中的E等待的也是讀鎖,所以E會被喚醒,獲取讀鎖繼續執行,如下圖所示:
13.同樣的,由於執行緒A獲取的是讀鎖,在E執行後,會喚醒執行緒A,A也可以獲得讀鎖,並繼續執行,如下圖所示:
14.最後大家各自執行,悄然退場。
非公平讀寫鎖
接下來我們再來看一下非公平策略讀寫鎖機制又是如何的,為了更好的對比,我們沿用公平鎖的流程。
由於獲取讀鎖的邏輯比較複雜,我們在這裡先簡單進行歸納:
a. 如果當前全域性處於無鎖狀態,則當前執行緒獲取讀鎖
b. 如果當前全域性處於讀鎖狀態,且佇列中沒有等待執行緒,則當前執行緒獲取讀鎖
c. 如果當前全域性處於寫鎖佔用狀態(並且不是當前執行緒佔有),則當前執行緒入隊尾
d. 如果當前全域性處於讀鎖狀態,且等待佇列中第一個等待執行緒想獲取寫鎖,那麼當前執行緒能夠獲取到讀鎖的條件為:當前執行緒獲取了寫鎖,還未釋放;當前執行緒獲取了讀鎖,這一次只是重入讀鎖而已;其它情況當前執行緒入隊尾。之所以這樣處理一方面是為了效率,一方面是為了避免想獲取寫鎖的執行緒飢餓,老是得不到執行的機會
e. 如果當前全域性處於讀鎖狀態,且等待佇列中第一個等待執行緒不是寫鎖,則當前執行緒可以搶佔讀鎖
獲取寫鎖相對就比較簡單了,規則如下:
h. 如果當前處於無鎖狀態,則當前執行緒獲取寫鎖
i. 如果當前全域性處於讀鎖狀態,當前執行緒入隊尾
j. 如果當前全域性處於寫鎖狀態,除非是重入獲取寫鎖,否則入隊尾
接下來我們看一遍流程:
1.執行緒A請求一個讀鎖,全域性處於無鎖狀態,根據規則a,執行緒A獲取了鎖,如下圖所示:
2.執行緒B請求一個讀鎖,根據規則b,執行緒B可以獲取到讀鎖
3.這時候,執行緒C請求一個寫鎖,由於當前其他兩個執行緒擁有讀鎖,寫鎖獲取失敗,執行緒C入佇列(根據規則i),如下所示:
AQS初始化會建立一個空的頭節點,C入佇列,然後會休眠,等待其他執行緒釋放鎖喚醒。
4.執行緒D也來了,執行緒D想獲取一個讀鎖,根據讀鎖規則d,佇列中第一個等待執行緒C請求的是寫鎖,為避免寫鎖遲遲獲取不到,並且執行緒D不是重入獲取讀鎖,所以執行緒D也入隊,如下圖所示:
5.這時候,執行緒A執行完了,釋放了讀鎖,由於B仍然佔有讀鎖,所以釋放後讀鎖仍然沒有完全釋放,寫鎖仍然沒有機會執行,如下圖所示:
6.這次,B也執行完了,執行完後,讀鎖全部釋放,這時候會喚醒排在同步器隊頭的節點C,C成功獲取一個寫鎖,如下圖所示:
7.一旦任何一個執行緒獲取了寫鎖,除了該執行緒自己,其它執行緒都將無法獲取讀鎖和寫鎖,這時候,執行緒C再次請求一個讀鎖,這是允許的,但反過來如果一個執行緒先獲取了讀鎖,再獲取寫鎖則是不行的。這時候的狀態如下圖所示:
8.這時候假設執行緒E也來了,E想獲取讀鎖,由於當前處於寫鎖狀態,直接入隊,如下所示:
9.這會C終於把活幹完了,把讀鎖和寫鎖都給釋放了,然後執行緒D被喚醒,獲取了讀鎖,如下圖所示:
10.這時候,如果再來一個執行緒,比如A,也想獲取讀鎖,雖然等待佇列中,E執行緒剛好還沒被喚醒,但A執行緒是可以搶佔讀鎖的(這裡假設搶佔到了),這個跟公平鎖有明顯的區別,如下圖所示:
11.這時候,如果D再次呼叫了一次獲取讀鎖,由於D屬於可重入狀態,所以直接把讀鎖+1即可,如下圖所示:
12.由於當前狀態下處於讀鎖狀態,前面的執行緒D其實醒來後,是會同時喚醒執行緒E的,所以執行緒E也醒過來繼續幹活了,如下圖所示:
13.同步佇列中沒有等待執行緒了,各個執行緒執行完後,一切相安無事了。
總結
考慮到業務的多樣化,java5中提供的併發包中的工具類大部分都同時提供了公平及非公平策略,這兩種策略下,一般而言,非公平鎖吞吐會比較大,所以預設情況下都是使用的非公平策略。
本篇試圖以儘量簡單的方式來闡明讀寫鎖的實現機制,為了直觀,我們只考慮簡單抽象的方式,實際在實現的時候,會使用CAS去競爭鎖。特別是在非公平策略中的第10個步驟,這種情況下有可能E先獲取了讀鎖。很多時候,我們在大致瞭解了實現步驟,流程之後,再去品味原始碼,就會更加的輕鬆。
最後還是建議大家在瞭解了思路之後,自己多看看原始碼,多思考,學到的才是屬於自己的東西。
相關文章
- Java併發——讀寫鎖ReentrantReadWriteLockJava
- 讀寫鎖 ReentrantReadWriteLock
- Java 讀寫鎖 ReentrantReadWriteLock 原始碼分析Java原始碼
- 讀寫鎖 ReentrantReadWriteLock 與 互斥鎖 的效率
- Java併發程式設計-讀寫鎖(ReentrantReadWriteLock)Java程式設計
- 深入理解讀寫鎖ReentrantReadWriteLock
- ReentrantReadWriteLock讀寫鎖及其在 RxCach
- Lock介面、重入鎖ReentrantLock、讀寫鎖ReentrantReadWriteLockReentrantLock
- Java併發指南10:Java 讀寫鎖 ReentrantReadWriteLock 原始碼分析Java原始碼
- 原始碼分析:ReentrantReadWriteLock之讀寫鎖原始碼
- Java中的讀寫鎖ReentrantReadWriteLock詳解,存在一個小缺陷Java
- Java併發程式設計之鎖機制之ReentrantReadWriteLock(讀寫鎖)Java程式設計
- ReentrantReadWriteLock讀寫鎖及其在 RxCache 中的使用
- java原始碼-ReentrantReadWriteLock寫鎖介紹Java原始碼
- Java併發包原始碼學習系列:ReentrantReadWriteLock讀寫鎖解析Java原始碼
- 深刨顯式鎖ReentrantLock原理及其與內建鎖的區別,以及讀寫鎖ReentrantReadWriteLock使用場景ReentrantLock
- AQS之ReentrantReadWriteLock寫鎖AQS
- Java鎖之ReentrantReadWriteLockJava
- 輕鬆搞懂Java中的自旋鎖Java
- java中的鎖及實現原理Java
- Java中的讀/寫鎖Java
- Java讀寫鎖ReadWriteLockJava
- 【java併發程式設計實戰4】偏向鎖-輕量鎖-重量鎖的那點祕密(synchronize實現原理)Java程式設計
- 掌握Snagit 2023:輕鬆實現螢幕錄製與截圖Git
- 全面解讀volatile和synchronize,輕鬆掌握Volatile與Synchronizedsynchronized
- 淺談Java中的鎖:Synchronized、重入鎖、讀寫鎖Javasynchronized
- 【JavaSE】Lock鎖和synchronized鎖的比較,lock鎖的特性,讀寫鎖的實現。Javasynchronized
- Concurrency(十五: Java中的讀寫鎖)Java
- Java 讀寫鎖 ReadWriteLock 原理與應用場景詳解Java
- Java併發基礎-鎖的使用及原理(可重入鎖、讀寫鎖、內建鎖、訊號量等)Java
- 輕量級分散式鎖的設計原理分析與實現分散式
- MySQL內部實現讀鎖和寫鎖的具體鎖定型別介紹MySql型別
- Nuxt.js必讀:輕鬆掌握執行時配置與 useRuntimeConfigUXJS
- Excel讀寫合集:Excel讀寫小白從不知所措到輕鬆上手Excel
- 輕鬆實現報表整合
- 手寫 PromiseA+ 實現,輕鬆透過 872 條用例Promise
- 分散式鎖的實現原理分散式
- Java併發-顯式鎖篇【可重入鎖+讀寫鎖】Java
- 輕鬆實現 Web 效能優化Web優化