java讀寫(xiě)鎖(ReentrantReadWriteLock)的實(shí)現(xiàn)原理

轉(zhuǎn)自? https://www.cnblogs.com/faunjoe88/p/7928757.html

公平讀寫(xiě)鎖

ReentrantReadWriteLock的鎖策略有兩種,分為公平策略和非公平策略,兩者有些小區(qū)別,為便于理解,

本小節(jié)將以示例的形式來(lái)說(shuō)明多線程下,使用公平策略的讀寫(xiě)鎖是如何處理的。

首先看一下即將出場(chǎng)的伙伴們,我們一共會(huì)出場(chǎng)幾個(gè)線程,還有用于實(shí)現(xiàn)讀寫(xiě)機(jī)制的AQS同步器隊(duì)列。

每個(gè)線程中的 R(0)W(0)表示當(dāng)前線程占用了多少讀寫(xiě)鎖。

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

1.線程A請(qǐng)求一個(gè)讀鎖,此時(shí)無(wú)人競(jìng)爭(zhēng)鎖,A獲取讀鎖1,即線程A重入次數(shù)為1,如下所示:


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


3.這時(shí)候,線程C請(qǐng)求一個(gè)寫(xiě)鎖,由于當(dāng)前其他兩個(gè)線程擁有讀鎖,寫(xiě)鎖獲取失敗,線程C入隊(duì)列,如下所示:


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

4.線程D也來(lái)了,線程D想獲取一個(gè)讀鎖,雖然當(dāng)于處于讀鎖占有階段,但是目前D不占有任何數(shù)量的讀鎖,

而且同步器隊(duì)列中已經(jīng)有等待節(jié)點(diǎn),這時(shí)候,由于公平策略,D不得已,一個(gè)字,等,如下圖所示:


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

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

7.一旦任何一個(gè)線程獲取了寫(xiě)鎖,除了該線程自己,其它線程都將無(wú)法獲取讀鎖和寫(xiě)鎖,這時(shí)候,線程C再次請(qǐng)求一個(gè)讀鎖,這是允許的,

但反過(guò)來(lái)如果一個(gè)線程先獲取了讀鎖,再獲取寫(xiě)法則是不行的。這時(shí)候的狀態(tài)如下圖所示:

8.這時(shí)候假設(shè)線程E也來(lái)了,E想獲取讀鎖,由于當(dāng)前處于寫(xiě)鎖狀態(tài),直接入隊(duì),如下所示:

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

10.這時(shí)候,如果再來(lái)一個(gè)線程,比如A,也想獲取讀鎖,由于節(jié)點(diǎn)中還有線程E在等待,而且當(dāng)前線程A沒(méi)有獲取任何讀鎖,不是重入狀態(tài),所以只能置入隊(duì)尾,如下圖所示:

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

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

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

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

非公平讀寫(xiě)鎖

接下來(lái)我們?cè)賮?lái)看一下非公平策略讀寫(xiě)鎖機(jī)制又是如何的,為了更好的對(duì)比,我們沿用公平鎖的流程。

由于獲取讀鎖的邏輯比較復(fù)雜,我們?cè)谶@里先簡(jiǎn)單進(jìn)行歸納:

a. 如果當(dāng)前全局處于無(wú)鎖狀態(tài),則當(dāng)前線程獲取讀鎖

b. 如果當(dāng)前全局處于讀鎖狀態(tài),且隊(duì)列中沒(méi)有等待線程,則當(dāng)前線程獲取讀鎖

c. 如果當(dāng)前全局處于寫(xiě)鎖占用狀態(tài)(并且不是當(dāng)前線程占有),則當(dāng)前線程入隊(duì)尾

d. 如果當(dāng)前全局處于讀鎖狀態(tài),且等待隊(duì)列中第一個(gè)等待線程想獲取寫(xiě)鎖,那么當(dāng)前線程能夠獲取到讀鎖的條件為:當(dāng)前線程獲取了寫(xiě)鎖,還未釋放;當(dāng)前線程獲取了讀鎖,這一次只是重入讀鎖而已;其它情況當(dāng)前線程入隊(duì)尾。之所以這樣處理一方面是為了效率,一方面是為了避免想獲取寫(xiě)鎖的線程饑餓,老是得不到執(zhí)行的機(jī)會(huì)

e. 如果當(dāng)前全局處于讀鎖狀態(tài),且等待隊(duì)列中第一個(gè)等待線程不是寫(xiě)鎖,則當(dāng)前線程可以搶占讀鎖

獲取寫(xiě)鎖相對(duì)就比較簡(jiǎn)單了,規(guī)則如下:

h. 如果當(dāng)前處于無(wú)鎖狀態(tài),則當(dāng)前線程獲取寫(xiě)鎖

i. 如果當(dāng)前全局處于讀鎖狀態(tài),當(dāng)前線程入隊(duì)尾

j. 如果當(dāng)前全局處于寫(xiě)鎖狀態(tài),除非是重入獲取寫(xiě)鎖,否則入隊(duì)尾

接下來(lái)我們看一遍流程:

1.線程A請(qǐng)求一個(gè)讀鎖,全局處于無(wú)鎖狀態(tài),根據(jù)規(guī)則a,線程A獲取了鎖,如下圖所示:

2.線程B請(qǐng)求一個(gè)讀鎖,根據(jù)規(guī)則b,線程B可以獲取到讀鎖

3.這時(shí)候,線程C請(qǐng)求一個(gè)寫(xiě)鎖,由于當(dāng)前其他兩個(gè)線程擁有讀鎖,寫(xiě)鎖獲取失敗,線程C入隊(duì)列(根據(jù)規(guī)則i),如下所示:

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

4.線程D也來(lái)了,線程D想獲取一個(gè)讀鎖,根據(jù)讀鎖規(guī)則d,隊(duì)列中第一個(gè)等待線程C請(qǐng)求的是寫(xiě)鎖,為避免寫(xiě)鎖遲遲獲取不到,并且線程D不是重入獲取讀鎖,所以線程D也入隊(duì),如下圖所示:

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

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

7.一旦任何一個(gè)線程獲取了寫(xiě)鎖,除了該線程自己,其它線程都將無(wú)法獲取讀鎖和寫(xiě)鎖,這時(shí)候,線程C再次請(qǐng)求一個(gè)讀鎖,這是允許的,

但反過(guò)來(lái)如果一個(gè)線程先獲取了讀鎖,再獲取寫(xiě)鎖則是不行的。這時(shí)候的狀態(tài)如下圖所示:

8.這時(shí)候假設(shè)線程E也來(lái)了,E想獲取讀鎖,由于當(dāng)前處于寫(xiě)鎖狀態(tài),直接入隊(duì),如下所示:

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

10.這時(shí)候,如果再來(lái)一個(gè)線程,比如A,也想獲取讀鎖,雖然等待隊(duì)列中,E線程剛好還沒(méi)被喚醒,

但A線程是可以搶占讀鎖的(這里假設(shè)搶占到了),這個(gè)跟公平鎖有明顯的區(qū)別,如下圖所示:

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

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

13.同步隊(duì)列中沒(méi)有等待線程了,各個(gè)線程執(zhí)行完后,一切相安無(wú)事了。

總結(jié)

考慮到業(yè)務(wù)的多樣化,java5中提供的并發(fā)包中的工具類大部分都同時(shí)提供了公平及非公平策略,這兩種策略下,一般而言,非公平鎖吞吐會(huì)比較大,所以默認(rèn)情況下都是使用的非公平策略。

本篇試圖以盡量簡(jiǎn)單的方式來(lái)闡明讀寫(xiě)鎖的實(shí)現(xiàn)機(jī)制,為了直觀,我們只考慮簡(jiǎn)單抽象的方式,實(shí)際在實(shí)現(xiàn)的時(shí)候,會(huì)使用CAS去競(jìng)爭(zhēng)鎖。特別是在非公平策略中的第10個(gè)步驟,這種情況下有可能E先獲取了讀鎖。很多時(shí)候,我們?cè)诖笾铝私饬藢?shí)現(xiàn)步驟,流程之后,再去品味源碼,就會(huì)更加的輕松。

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

友情鏈接更多精彩內(nèi)容