若是以前使用過讀寫鎖, 那麼能夠直接看本篇文章. 若是以前未使用過, 那麼請配合個人另外一篇文章一塊兒看: [源碼分析]讀寫鎖ReentrantReadWriteLockhtml
我先給出一個demo, 這樣你們就能夠根據我給的這段代碼, 邊調試邊看源碼了. 仍是那句話: 注意"My" , 我把ReentrantReadWriteLock類 更名爲了 "MyReentrantReadWriteLock"類 , "Lock"類 更名爲了"MyLock"類. 你們粘貼個人代碼的時候, 把相應的"My"都去掉就行了, 不然會編譯報錯哦.java
demo裏是一個公平讀寫鎖多線程
import java.util.HashMap; import java.util.Map; import java.util.Scanner; import java.util.concurrent.locks.Lock; import java.util.function.Supplier; public class ReentrantReadWriteLockTest2 { static final Scanner scanner = new Scanner(System.in); static volatile String cmd = ""; private static MyReentrantReadWriteLock lock = new MyReentrantReadWriteLock(true); private static MyReentrantReadWriteLock.ReadLock readLock = lock.readLock(); private static MyReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); public static void main(String[] args) { for (Map.Entry<String, Lock> entry : new HashMap<String, Lock>() {{ for (int i = 0; i < 10; i++) { put("r" + i, readLock); put("w" + i, writeLock); } }}.entrySet()) { new Thread(() -> func(entry::getValue, entry.getKey())).start(); } while (scanner.hasNext()) { cmd = scanner.nextLine(); } } public static void func(Supplier<Lock> myLockSupplier, String name) { String en_type = myLockSupplier.get().getClass().getSimpleName().toLowerCase().split("lock")[0]; String zn_type = (en_type.equals("read") ? "讀" : "寫"); blockUntilEquals(() -> cmd, "lock " + en_type + " " + name); myLockSupplier.get().lock(); System.out.println(name + "獲取了" + zn_type + "鎖"); blockUntilEquals(() -> cmd, "unlock " + en_type + " " + name); myLockSupplier.get().unlock(); System.out.println(name + "釋放了" + zn_type + "鎖"); } private static void blockUntilEquals(Supplier<String> cmdSupplier, final String expect) { while (!cmdSupplier.get().equals(expect)) quietSleep(1000); } private static void quietSleep(int mills) { try { Thread.sleep(mills); } catch (InterruptedException e) { e.printStackTrace(); } } }
使用例子在下面. 源碼分析
咱們能夠看到r1持有了讀鎖以後, r2來申請讀鎖, 也能夠成功. 說明讀鎖是能夠共享的.ui
接下來申請寫鎖. 申請的寫鎖會進入到`等待隊列`.spa
而後我們再申請讀鎖r3, r4. 因爲我們的demo是公平的讀寫鎖. `等待隊列`中有線程在等待寫鎖時, 後續的申請讀鎖的線程也都會直接進入`等待隊列`. 不會和先來的寫鎖線程爭搶.線程
大體就是這樣.更詳細的請看下面小節的詳解.3d
(圖1)指針
-----------2018.07.29 下午---更新-----關於圖片不清楚的問題------------------------調試
本文下面評論中提出這篇博客的圖片看着很不舒服. 我之後會注意的. 以前沒想到顯示圖片會有差異.
不過這裏臨時先給出一個解決辦法. 若是想看仔細看某一個圖片, 那麼請這樣作(能夠拿上面的圖1來試試, 本篇中除了圖2和圖3均可以的 ):
在圖片上右鍵 -> 新標籤中打開圖片 - > 在這個新標籤中鼠標指針是一個放大鏡, 左鍵點擊一下, 讓圖片放大.而後你會發現, 圖片很是很是很是清晰....
大概就是這樣的區別:
(圖2)
(圖3)
----------------------下面我們回到本篇主題---------------------------
我們實例化一個讀寫鎖後, 鎖的狀態大體以下圖:
此時鎖是空閒狀態.
若是這個時候r1來申請讀鎖.那麼就能夠直接成功, 變化以下的黑色陰影部分.
firstReader 是線程的引用. 讀鎖是共享的, 能夠有不少線程來獲取讀鎖. 而firstReader是記錄這些持有讀鎖線程中第一個得到讀鎖的線程的.
firstReaderHoldCount是 firstReader引用的線程的讀鎖得到次數(也就是firstReader重入的次數)
接下來若是r2來申請讀鎖, 會發生什麼?
r2會申請成功, 並且變化以下:
其中cacheHoldCounter是一個引用, 老是指向最後一個得到讀鎖的線程的計數器.
接下來讓w1線程申請寫鎖. 寫鎖和讀鎖是互斥的, 因此寫鎖沒法申請成功, 因而會進入到`等待隊列`.
因爲等待隊列是懶初始化, 因此這個時候纔會產生等待隊列的頭結點:
而後就是把w1對應的Node尾插到`等待隊列`中了:
而後再把當前節點的前驅節點的waitStatus置爲-1. -1表示後繼節點在等待線程被激活.
而後線程w1就放心地掛起了:
接下來我們再讓r3線程獲取讀鎖會怎麼樣呢?
(我們如今演示的是公平鎖, 若是有線程在隊列裏等待的話, 後續申請讀鎖的線程就不會直接拿到讀鎖, 而是進入到等待隊列中. 畢竟寫鎖先來的嘛, 不能插隊.)
線程r3進入到了`等待隊列`中.而後線程r3掛起了. 變化如上圖的黑色陰影部分所示.
接下來我們讓r4申請讀鎖, 最終結果和r3同樣, 就是進入到了`等待隊列`的最末尾. (可是這個r4在後續的講解中有用)
因此r4就不用講了, 和r3同樣:
接下來我們釋放r1的讀鎖:
而後釋放r2的讀鎖:
(cachedHoldCounter我沒有加陰影, 是由於, 他其實並非真的變爲null了, 仍是指向原來的那個元素, 可是這個已經不重要了.)
當線程r2釋放讀鎖的時候發現讀鎖已經被徹底釋放了, 因此會激活`等待隊列` 裏的第一個線程.
而且讓第一個線程對應的Node做爲新的Head. 淘汰掉原先的Head.
釋放w1的寫鎖:
線程w1釋放了讀鎖後, 激活了本身的後繼節點r3.
r3被激活後,開始準備獲取讀鎖.
把firstReader指向本身後, 把本身替換爲新的Head節點:
線程r3申請完讀鎖後, 查看後繼節點的nextWaiter是否等於Node.SHARED. 若是是, 那麼就會喚醒這個後繼節點.
因此接下來會喚醒r4:
如今r4被激活了, r4開始申請讀鎖了:
而後r4即將成爲新的Head節點:
到這裏, demo裏的演示部分就完成了.
最後, 我們依次把r3 和 r4 也都釋放了吧. 反正也剩的很少了.
釋放了r3的時候, 變化以下:
最後我們釋放掉r4:
(其中的cachedHoldCounter並非真正地變爲了null, 而是還在指向着原來的元素. 只是在這裏顯得沒用了, 因此那部分沒話)
線程r4執行完以後, 全部的線程就都釋放了. 鎖的狀態以下:
到這裏, 整個公平讀寫鎖的申請鎖, 釋放鎖的過程, 就都演示完了.