轉載:https://blog.csdn.net/yanyan19880509/article/details/52435135java
前面介紹了java中排它鎖,共享鎖的底層實現機制,本篇再進一步,學習很是有用的讀寫鎖。鑑於讀寫鎖比其餘的鎖要複雜,不想堆一大波的文字,本篇會試圖圖解式說明,把讀寫鎖的機制用另一種方式闡述,鑑於本人水平有限,若是哪裏有誤,請不吝賜教。多線程
ReentrantReadWriteLock的鎖策略有兩種,分爲公平策略和非公平策略,二者有些小區別,爲便於理解,本小節將以示例的形式來講明多線程下,使用公平策略的讀寫鎖是如何處理的。併發
首先看一下即將出場的夥伴們,咱們一共會出場幾個線程,還有用於實現讀寫機制的AQS同步器隊列。每一個線程中的 R(0)W(0)表示當前線程佔用了多少讀寫鎖。工具
接下來,咱們一步步來看在公平策略下多線程併發的讀寫機制是怎樣的。學習
1.線程A請求一個讀鎖,此時無人競爭鎖,A獲取讀鎖1,即線程A重入次數爲1,以下所示:spa
2.線程B請求一個讀鎖,因爲AQS中沒有等待節點,當前處於讀鎖佔有狀態(線程A佔有1個讀鎖),因此B成功獲取讀鎖,以下所示:.net
3.這時候,線程C請求一個寫鎖,因爲當前其餘兩個線程擁有讀鎖,寫鎖獲取失敗,線程C入隊列,以下所示:線程
AQS初始化會建立一個空的頭節點,C入隊列,而後會休眠,等待其餘線程釋放鎖喚醒。3d
4.線程D也來了,線程D想獲取一個讀鎖,雖然當於處於讀鎖佔有階段,可是目前D不佔有任何數量的讀鎖,並且同步器隊列中已經有等待節點,這時候,因爲公平策略,D不得已,一個字,等,以下圖所示:blog
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先獲取了讀鎖。不少時候,咱們在大體瞭解了實現步驟,流程以後,再去品味源碼,就會更加的輕鬆。
最後仍是建議你們在瞭解了思路以後,本身多看看源碼,多思考,學到的纔是屬於本身的東西。