ReentrantReadWriteLock

轉自:https://blog.csdn.net/yanyan19880509/article/details/52435135

前言

前面介紹了java中排它鎖,共享鎖的底層實現機制,本篇再進一步,學習非常有用的讀寫鎖。鑒于讀寫鎖比其他的鎖要復雜,不想堆一大波的文字,本篇會試圖圖解式說明,把讀寫鎖的機制用另外一種方式闡述,鑒于本人水平有限,如果哪里有誤,請不吝賜教。

公平讀寫鎖

ReentrantReadWriteLock的鎖策略有兩種,分為公平策略和非公平策略,兩者有些小區(qū)別,為便于理解,本小節(jié)將以示例的形式來說明多線程下,使用公平策略的讀寫鎖是如何處理的。
首先看一下即將出場的伙伴們,我們一共會出場幾個線程,還有用于實現讀寫機制的AQS同步器隊列。每個線程中的 R(0)W(0)表示當前線程占用了多少讀寫鎖。


image.png

接下來,我們一步步來看在公平策略下多線程并發(fā)的讀寫機制是怎樣的。

1.線程A請求一個讀鎖,此時無人競爭鎖,A獲取讀鎖1,即線程A重入次數為1,如下所示:


image.png

2.線程B請求一個讀鎖,由于AQS中沒有等待節(jié)點,當前處于讀鎖占有狀態(tài)(線程A占有1個讀鎖),所以B成功獲取讀鎖,如下所示:


image.png

3.這時候,線程C請求一個寫鎖,由于當前其他兩個線程擁有讀鎖,寫鎖獲取失敗,線程C入隊列,如下所示:
image.png

AQS初始化會創(chuàng)建一個空的頭節(jié)點,C入隊列,然后會休眠,等待其他線程釋放鎖喚醒。

4.線程D也來了,線程D想獲取一個讀鎖,雖然當于處于讀鎖占有階段,但是目前D不占有任何數量的讀鎖,而且同步器隊列中已經有等待節(jié)點,這時候,由于公平策略,D不得已,一個字,等,如下圖所示:


image.png

5.這時候,線程A執(zhí)行完了,釋放了讀鎖,由于B仍然占有讀鎖,所以釋放后讀鎖仍然沒有完全釋放,寫鎖仍然沒有機會執(zhí)行,如下圖所示:


image.png

6.這次,B也執(zhí)行完了,執(zhí)行完后,讀鎖全部釋放,這時候會喚醒排在同步器隊頭的節(jié)點C,C成功獲取一個寫鎖,如下圖所示:
image.png

7.一旦任何一個線程獲取了寫鎖,除了該線程自己,其它線程都將無法獲取讀鎖和寫鎖,這時候,線程C再次請求一個讀鎖,這是允許的,但反過來如果一個線程先獲取了讀鎖,再獲取寫法則是不行的。這時候的狀態(tài)如下圖所示:


image.png

8.這時候假設線程E也來了,E想獲取讀鎖,由于當前處于寫鎖狀態(tài),直接入隊,如下所示:
image.png

9.這會C終于把活干完了,把讀鎖和寫鎖都給釋放了,然后線程D被喚醒,獲取了讀鎖,如下圖所示:
image.png

10.這時候,如果再來一個線程,比如A,也想獲取讀鎖,由于節(jié)點中還有線程E在等待,而且當前線程A沒有獲取任何讀鎖,不是重入狀態(tài),所以只能置入隊尾,如下圖所示:
image.png

11.這時候,如果D再次調用了一次獲取讀鎖,由于D屬于可重入狀態(tài),所以直接把讀鎖+1即可,如下圖所示:
image.png

12.由于D獲取的是讀鎖,同步隊列中的E等待的也是讀鎖,所以E會被喚醒,獲取讀鎖繼續(xù)執(zhí)行,如下圖所示:


image.png

13.同樣的,由于線程A獲取的是讀鎖,在E執(zhí)行后,會喚醒線程A,A也可以獲得讀鎖,并繼續(xù)執(zhí)行,如下圖所示:
image.png

14.最后大家各自執(zhí)行,悄然退場。

非公平讀寫鎖

接下來我們再來看一下非公平策略讀寫鎖機制又是如何的,為了更好的對比,我們沿用公平鎖的流程。
由于獲取讀鎖的邏輯比較復雜,我們在這里先簡單進行歸納:

  1. 如果當前全局處于無鎖狀態(tài),則當前線程獲取讀鎖
  2. 如果當前全局處于讀鎖狀態(tài),且隊列中沒有等待線程,則當前線程獲取讀鎖
  3. 如果當前全局處于寫鎖占用狀態(tài)(并且不是當前線程占有),則當前線程入隊尾
  4. 如果當前全局處于讀鎖狀態(tài),且等待隊列中第一個等待線程想獲取寫鎖,那么當前線程能夠獲取到讀鎖的條件為:當前線程獲取了寫鎖,還未釋放;當前線程獲取了讀鎖,這一次只是重入讀鎖而已;其它情況當前線程入隊尾。之所以這樣處理一方面是為了效率,一方面是為了避免想獲取寫鎖的線程饑餓,老是得不到執(zhí)行的機會
  5. 如果當前全局處于讀鎖狀態(tài),且等待隊列中第一個等待線程不是寫鎖,則當前線程可以搶占讀鎖

獲取寫鎖相對就比較簡單了,規(guī)則如下:

  1. 如果當前處于無鎖狀態(tài),則當前線程獲取寫鎖
  2. 如果當前全局處于讀鎖狀態(tài),當前線程入隊尾
  3. 如果當前全局處于寫鎖狀態(tài),除非是重入獲取寫鎖,否則入隊尾

接下來我們看一遍流程:
1.線程A請求一個讀鎖,全局處于無鎖狀態(tài),根據規(guī)則a,線程A獲取了鎖,如下圖所示:


image.png

2.線程B請求一個讀鎖,根據規(guī)則b,線程B可以獲取到讀鎖


image.png

3.這時候,線程C請求一個寫鎖,由于當前其他兩個線程擁有讀鎖,寫鎖獲取失敗,線程C入隊列(根據規(guī)則i),如下所示:
image.png

AQS初始化會創(chuàng)建一個空的頭節(jié)點,C入隊列,然后會休眠,等待其他線程釋放鎖喚醒。

4.線程D也來了,線程D想獲取一個讀鎖,根據讀鎖規(guī)則d,隊列中第一個等待線程C請求的是寫鎖,為避免寫鎖遲遲獲取不到,并且線程D不是重入獲取讀鎖,所以線程D也入隊,如下圖所示:


image.png

5.這時候,線程A執(zhí)行完了,釋放了讀鎖,由于B仍然占有讀鎖,所以釋放后讀鎖仍然沒有完全釋放,寫鎖仍然沒有機會執(zhí)行,如下圖所示:


image.png

6.這次,B也執(zhí)行完了,執(zhí)行完后,讀鎖全部釋放,這時候會喚醒排在同步器隊頭的節(jié)點C,C成功獲取一個寫鎖,如下圖所示:
image.png

7.一旦任何一個線程獲取了寫鎖,除了該線程自己,其它線程都將無法獲取讀鎖和寫鎖,這時候,線程C再次請求一個讀鎖,這是允許的,但反過來如果一個線程先獲取了讀鎖,再獲取寫鎖則是不行的。這時候的狀態(tài)如下圖所示:


image.png

8.這時候假設線程E也來了,E想獲取讀鎖,由于當前處于寫鎖狀態(tài),直接入隊,如下所示:
image.png

9.這會C終于把活干完了,把讀鎖和寫鎖都給釋放了,然后線程D被喚醒,獲取了讀鎖,如下圖所示:
image.png

10.這時候,如果再來一個線程,比如A,也想獲取讀鎖,雖然等待隊列中,E線程剛好還沒被喚醒,但A線程是可以搶占讀鎖的(這里假設搶占到了),這個跟公平鎖有明顯的區(qū)別,如下圖所示:
image.png

11.這時候,如果D再次調用了一次獲取讀鎖,由于D屬于可重入狀態(tài),所以直接把讀鎖+1即可,如下圖所示:
image.png

12.由于當前狀態(tài)下處于讀鎖狀態(tài),前面的線程D其實醒來后,是會同時喚醒線程E的,所以線程E也醒過來繼續(xù)干活了,如下圖所示:


image.png

13.同步隊列中沒有等待線程了,各個線程執(zhí)行完后,一切相安無事了。
總結
考慮到業(yè)務的多樣化,java5中提供的并發(fā)包中的工具類大部分都同時提供了公平及非公平策略,這兩種策略下,一般而言,非公平鎖吞吐會比較大,所以默認情況下都是使用的非公平策略。

本篇試圖以盡量簡單的方式來闡明讀寫鎖的實現機制,為了直觀,我們只考慮簡單抽象的方式,實際在實現的時候,會使用CAS去競爭鎖。特別是在非公平策略中的第10個步驟,這種情況下有可能E先獲取了讀鎖。很多時候,我們在大致了解了實現步驟,流程之后,再去品味源碼,就會更加的輕松。

最后還是建議大家在了解了思路之后,自己多看看源碼,多思考,學到的才是屬于自己的東西。


作者:那個天真的人
來源:CSDN
原文:https://blog.csdn.net/yanyan19880509/article/details/52435135
版權聲明:本文為博主原創(chuàng)文章,轉載請附上博文鏈接!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容