[圖解Java]讀寫鎖ReentrantReadWriteLock

圖解ReentrantReadWriteLock

若是以前使用過讀寫鎖, 那麼能夠直接看本篇文章. 若是以前未使用過, 那麼請配合個人另外一篇文章一塊兒看: [源碼分析]讀寫鎖ReentrantReadWriteLockhtml

0. demo

我先給出一個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)指針

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執行完以後, 全部的線程就都釋放了. 鎖的狀態以下: 

 到這裏, 整個公平讀寫鎖的申請鎖, 釋放鎖的過程, 就都演示完了. 

相關文章
相關標籤/搜索