標籤:asn 使用 count name gets 改名 現在 als try
圖解ReentrantReadWriteLock
如果之前使用過讀寫鎖, 那麼可以直接看本篇文章. 如果之前未使用過, 那麼請配合我的另一篇文章一起看: [源碼分析]讀寫鎖ReentrantReadWriteLock
0. demo
我先給出一個demo, 這樣大家就可以根據我給的這段代碼, 邊調試邊看源碼了. 還是那句話: 注意"My" , 我把ReentrantReadWriteLock類 改名為了 "MyReentrantReadWriteLock"類 , "Lock"類 改名為了"MyLock"類. 大家粘貼My Code的時候, 把相應的"My"都去掉就好了, 否則會編譯報錯哦.
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來申請讀鎖, 也可以成功. 說明讀鎖是可以共用的.
接下來寫鎖
1. 開始圖解 (公平讀寫鎖)
咱們執行個體化一個讀寫鎖後, 鎖的狀態大致如:
此時鎖是空閑狀態.
如果這個時候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執行完之後, 所有的線程就都釋放了. 鎖的狀態如下:
到這裡, 整個公平讀寫鎖的申請鎖, 釋放鎖的過程, 就都示範完了.
[圖解Java]讀寫鎖ReentrantReadWriteLock