轉(zhuǎn)自? https://www.cnblogs.com/faunjoe88/p/7928757.html
公平讀寫鎖
ReentrantReadWriteLock的鎖策略有兩種淹办,分為公平策略和非公平策略,兩者有些小區(qū)別,為便于理解,
本小節(jié)將以示例的形式來說明多線程下,使用公平策略的讀寫鎖是如何處理的。
首先看一下即將出場的伙伴們,我們一共會出場幾個線程襟士,還有用于實現(xiàn)讀寫機制的AQS同步器隊列。
每個線程中的 R(0)W(0)表示當(dāng)前線程占用了多少讀寫鎖嚷量。
接下來陋桂,我們一步步來看在公平策略下多線程并發(fā)的讀寫機制是怎樣的。
1.線程A請求一個讀鎖蝶溶,此時無人競爭鎖嗜历,A獲取讀鎖1,即線程A重入次數(shù)為1,如下所示:
2.線程B請求一個讀鎖秸脱,由于AQS中沒有等待節(jié)點落包,當(dāng)前處于讀鎖占有狀態(tài)(線程A占有1個讀鎖),所以B成功獲取讀鎖摊唇,如下所示:
3.這時候咐蝇,線程C請求一個寫鎖,由于當(dāng)前其他兩個線程擁有讀鎖巷查,寫鎖獲取失敗有序,線程C入隊列,如下所示:
AQS初始化會創(chuàng)建一個空的頭節(jié)點岛请,C入隊列旭寿,然后會休眠,等待其他線程釋放鎖喚醒崇败。
4.線程D也來了盅称,線程D想獲取一個讀鎖,雖然當(dāng)于處于讀鎖占有階段后室,但是目前D不占有任何數(shù)量的讀鎖缩膝,
而且同步器隊列中已經(jīng)有等待節(jié)點,這時候岸霹,由于公平策略疾层,D不得已,一個字贡避,等痛黎,如下圖所示:
5.這時候,線程A執(zhí)行完了刮吧,釋放了讀鎖湖饱,由于B仍然占有讀鎖,所以釋放后讀鎖仍然沒有完全釋放杀捻,寫鎖仍然沒有機會執(zhí)行琉历,如下圖所示:
6.這次,B也執(zhí)行完了水醋,執(zhí)行完后,讀鎖全部釋放彪置,這時候會喚醒排在同步器隊頭的節(jié)點C拄踪,C成功獲取一個寫鎖,如下圖所示:
7.一旦任何一個線程獲取了寫鎖拳魁,除了該線程自己惶桐,其它線程都將無法獲取讀鎖和寫鎖,這時候,線程C再次請求一個讀鎖姚糊,這是允許的贿衍,
但反過來如果一個線程先獲取了讀鎖,再獲取寫法則是不行的救恨。這時候的狀態(tài)如下圖所示:
8.這時候假設(shè)線程E也來了贸辈,E想獲取讀鎖,由于當(dāng)前處于寫鎖狀態(tài)肠槽,直接入隊擎淤,如下所示:
9.這會C終于把活干完了,把讀鎖和寫鎖都給釋放了秸仙,然后線程D被喚醒嘴拢,獲取了讀鎖,如下圖所示:
10.這時候寂纪,如果再來一個線程席吴,比如A评甜,也想獲取讀鎖哎甲,由于節(jié)點中還有線程E在等待,而且當(dāng)前線程A沒有獲取任何讀鎖奴饮,不是重入狀態(tài)襟交,所以只能置入隊尾迈倍,如下圖所示:
11.這時候,如果D再次調(diào)用了一次獲取讀鎖捣域,由于D屬于可重入狀態(tài)啼染,所以直接把讀鎖+1即可,如下圖所示:
12.由于D獲取的是讀鎖焕梅,同步隊列中的E等待的也是讀鎖迹鹅,所以E會被喚醒,獲取讀鎖繼續(xù)執(zhí)行贞言,如下圖所示:
13.同樣的斜棚,由于線程A獲取的是讀鎖,在E執(zhí)行后该窗,會喚醒線程A弟蚀,A也可以獲得讀鎖,并繼續(xù)執(zhí)行酗失,如下圖所示:
14.最后大家各自執(zhí)行义钉,悄然退場。
非公平讀寫鎖
接下來我們再來看一下非公平策略讀寫鎖機制又是如何的规肴,為了更好的對比捶闸,我們沿用公平鎖的流程夜畴。
由于獲取讀鎖的邏輯比較復(fù)雜,我們在這里先簡單進(jìn)行歸納:
a. 如果當(dāng)前全局處于無鎖狀態(tài)删壮,則當(dāng)前線程獲取讀鎖
b. 如果當(dāng)前全局處于讀鎖狀態(tài)贪绘,且隊列中沒有等待線程,則當(dāng)前線程獲取讀鎖
c. 如果當(dāng)前全局處于寫鎖占用狀態(tài)(并且不是當(dāng)前線程占有)央碟,則當(dāng)前線程入隊尾
d. 如果當(dāng)前全局處于讀鎖狀態(tài)税灌,且等待隊列中第一個等待線程想獲取寫鎖,那么當(dāng)前線程能夠獲取到讀鎖的條件為:當(dāng)前線程獲取了寫鎖硬耍,還未釋放垄琐;當(dāng)前線程獲取了讀鎖,這一次只是重入讀鎖而已经柴;其它情況當(dāng)前線程入隊尾狸窘。之所以這樣處理一方面是為了效率,一方面是為了避免想獲取寫鎖的線程饑餓坯认,老是得不到執(zhí)行的機會
e. 如果當(dāng)前全局處于讀鎖狀態(tài)翻擒,且等待隊列中第一個等待線程不是寫鎖,則當(dāng)前線程可以搶占讀鎖
獲取寫鎖相對就比較簡單了牛哺,規(guī)則如下:
h. 如果當(dāng)前處于無鎖狀態(tài)陋气,則當(dāng)前線程獲取寫鎖
i. 如果當(dāng)前全局處于讀鎖狀態(tài),當(dāng)前線程入隊尾
j. 如果當(dāng)前全局處于寫鎖狀態(tài)引润,除非是重入獲取寫鎖巩趁,否則入隊尾
接下來我們看一遍流程:
1.線程A請求一個讀鎖,全局處于無鎖狀態(tài)淳附,根據(jù)規(guī)則a议慰,線程A獲取了鎖,如下圖所示:
2.線程B請求一個讀鎖奴曙,根據(jù)規(guī)則b别凹,線程B可以獲取到讀鎖
3.這時候,線程C請求一個寫鎖洽糟,由于當(dāng)前其他兩個線程擁有讀鎖炉菲,寫鎖獲取失敗,線程C入隊列(根據(jù)規(guī)則i)坤溃,如下所示:
AQS初始化會創(chuàng)建一個空的頭節(jié)點拍霜,C入隊列,然后會休眠薪介,等待其他線程釋放鎖喚醒沉御。
4.線程D也來了,線程D想獲取一個讀鎖昭灵,根據(jù)讀鎖規(guī)則d吠裆,隊列中第一個等待線程C請求的是寫鎖,為避免寫鎖遲遲獲取不到烂完,并且線程D不是重入獲取讀鎖试疙,所以線程D也入隊,如下圖所示:
5.這時候抠蚣,線程A執(zhí)行完了祝旷,釋放了讀鎖,由于B仍然占有讀鎖嘶窄,所以釋放后讀鎖仍然沒有完全釋放怀跛,寫鎖仍然沒有機會執(zhí)行,如下圖所示:
6.這次柄冲,B也執(zhí)行完了吻谋,執(zhí)行完后,讀鎖全部釋放现横,這時候會喚醒排在同步器隊頭的節(jié)點C漓拾,C成功獲取一個寫鎖,如下圖所示:
7.一旦任何一個線程獲取了寫鎖戒祠,除了該線程自己骇两,其它線程都將無法獲取讀鎖和寫鎖,這時候姜盈,線程C再次請求一個讀鎖低千,這是允許的,
但反過來如果一個線程先獲取了讀鎖馏颂,再獲取寫鎖則是不行的示血。這時候的狀態(tài)如下圖所示:
8.這時候假設(shè)線程E也來了,E想獲取讀鎖饱亮,由于當(dāng)前處于寫鎖狀態(tài)矾芙,直接入隊,如下所示:
9.這會C終于把活干完了近上,把讀鎖和寫鎖都給釋放了剔宪,然后線程D被喚醒,獲取了讀鎖壹无,如下圖所示:
10.這時候葱绒,如果再來一個線程,比如A斗锭,也想獲取讀鎖地淀,雖然等待隊列中,E線程剛好還沒被喚醒岖是,
但A線程是可以搶占讀鎖的(這里假設(shè)搶占到了)帮毁,這個跟公平鎖有明顯的區(qū)別实苞,如下圖所示:
11.這時候,如果D再次調(diào)用了一次獲取讀鎖烈疚,由于D屬于可重入狀態(tài)黔牵,所以直接把讀鎖+1即可,如下圖所示:
12.由于當(dāng)前狀態(tài)下處于讀鎖狀態(tài)爷肝,前面的線程D其實醒來后猾浦,是會同時喚醒線程E的,所以線程E也醒過來繼續(xù)干活了灯抛,如下圖所示:
13.同步隊列中沒有等待線程了金赦,各個線程執(zhí)行完后,一切相安無事了对嚼。
總結(jié)
考慮到業(yè)務(wù)的多樣化夹抗,java5中提供的并發(fā)包中的工具類大部分都同時提供了公平及非公平策略,這兩種策略下猪半,一般而言兔朦,非公平鎖吞吐會比較大,所以默認(rèn)情況下都是使用的非公平策略磨确。
本篇試圖以盡量簡單的方式來闡明讀寫鎖的實現(xiàn)機制沽甥,為了直觀,我們只考慮簡單抽象的方式乏奥,實際在實現(xiàn)的時候摆舟,會使用CAS去競爭鎖。特別是在非公平策略中的第10個步驟邓了,這種情況下有可能E先獲取了讀鎖恨诱。很多時候,我們在大致了解了實現(xiàn)步驟骗炉,流程之后照宝,再去品味源碼,就會更加的輕松句葵。