1. 程式人生 > >[源碼分析]讀寫鎖ReentrantReadWriteLock

[源碼分析]讀寫鎖ReentrantReadWriteLock

ktr next hashmap wait 異常 move 同時 有時 func

一.簡介

讀寫鎖. 讀鎖之間是共享的. 寫鎖是獨占的.

首先聲明一點: 我在分析源碼的時候, 把jdk源碼復制出來進行中文的註釋, 有時還進行編譯調試什麽的, 為了避免和jdk原生的類混淆, 我在類前面加了"My". 比如把ReentrantLock改名為了MyReentrantLock, 在源碼分析的章節裏, 我基本不會對源碼進行修改, 所以請忽視這個"My"即可.

1. ReentrantReadWriteLock類裏的字段

技術分享圖片

unsafe在這裏是用來給TID_OFFSET賦值的.

那麽TID_OFFSET是什麽? 就是tid變量在Thread類裏的偏移量. tid就是線程id.

下面就是獲取TID_OFFSET的源碼: (這裏我進行了一點改動, 改為了反射)

技術分享圖片

同步器:

技術分享圖片

讀鎖:

技術分享圖片

寫鎖:

技術分享圖片

2. ReentrantReadWriteLock類的構造器

技術分享圖片

這是一個帶參構造器, 可以選擇公平鎖還是非公平鎖. 同時實例化了讀鎖和寫鎖.

而默認構造器是直接調用上面的帶參構造器, 采用了非公平鎖:

技術分享圖片

二. 公平讀鎖的申請和釋放

1. 場景demo

現在模擬一個場景. 兩個線程, 同時申請讀鎖, 場景的demo如下:

public class Main {
    static final Scanner scanner = new Scanner(System.in);
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    private static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    static volatile String cmd = "";

    public static void main(String[] args) {
        new Thread(Main::funcA).start();
        new Thread(Main::funcB).start();

        while (scanner.hasNext()) {
            cmd = scanner.nextLine();
        }
    }

    public static void funcA() {
        blockUntilEquals(() -> cmd, "lock a");
        readLock.lock();
        System.out.println("funcA獲取了讀鎖");
        blockUntilEquals(() -> cmd, "unlock a");
        readLock.unlock();
        System.out.println("funcA釋放了讀鎖");
    }

    public static void funcB() {
        blockUntilEquals(() -> cmd, "lock b");
        readLock.lock();
        System.out.println("funcB獲取了讀鎖");
        blockUntilEquals(() -> cmd, "unlock b");
        readLock.unlock();
        System.out.println("funcB釋放了讀鎖");
    }

    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();
        }
    }
}

運行上面這段代碼.

然後輸入"lock a", 然後按下回車, (不帶引號), 線程a就獲取到了讀鎖.

然後輸入"lock b", 然後按下回車, (不帶引號), 線程b就獲取到了讀鎖.

如下圖所示, 藍字為我輸入的內容.

技術分享圖片

可見, 兩個讀鎖之間不是互斥的, 是可以共享同一個鎖的.

接下來咱們讓這兩個線程a和b 分別釋放掉讀鎖.

輸入"unlock a", 然後按下回車, (不帶括號) , 然後輸入"unlock b", 然後按下回車. 就分別釋放了兩個鎖了. 如下圖所示:

技術分享圖片

2. 獲取第一個讀鎖

我帶著大家一起調試. 請在funcA()方法裏的readLock.lock()這裏打下斷點, 然後用debug模式運行.

技術分享圖片

然後再控制臺輸入 "lock a" , 註意不帶引號, 然後按下回車:

技術分享圖片

然後就發現代碼執行到readLock.lock()處就阻塞了:

技術分享圖片

按下F7 , 進入readLock.lock()方法, 可以看到讀鎖的lock方法的實現:

技術分享圖片

可以看到, 調用了acquireShared方法來以共享模式申請了鎖.

acquireShared方法源代碼如下:

技術分享圖片

咱們按F7(Step Into進入tryAcquireShared方法, 看看裏面的執行過程吧:

    protected final int tryAcquireShared(int unused) {// 參數沒用
        // 獲取當前線程的引用
        Thread current = Thread.currentThread();
        // 獲取鎖的狀態. c 的低 16 位值,代表寫鎖的狀態. 高16位代表讀鎖的狀態
        int c = getState();

        // exclusiveCount(c) 是寫鎖的state. 不等於 0,說明有線程持有寫鎖. (當前場景下肯定等於0, 所以跳過這段if語句)
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;

        // 讀鎖的state
        int r = sharedCount(c);

        // 讀鎖獲取是否應該被阻塞, 其實就是根據`等待隊列`來判斷是否應該被阻塞的 ( 當前場景下沒有比當前線程等待更久的線程, 所以不會被阻塞.)
        if (!readerShouldBlock() &&
                // 判斷是否會溢出 (2^16-1). (當前的r==0, 所以沒有溢出)
                r < MAX_COUNT &&
                // 下面這行 CAS 是將 state 屬性的高 16 位加 1,低 16 位不變,如果成功就代表獲取到了讀鎖. (當前場景下, 沒有線程競爭, 所以肯定成功.)
                compareAndSetState(c, c + SHARED_UNIT)) {

            /* ----------------------
             *  進到這裏就是獲取到了讀鎖
             * ----------------------*/

            // r == 0 說明此線程是第一個獲取讀鎖的,或者說在它之前來的讀鎖的都走光了. (當前場景r就是等於0, 所以會執行這段if)
            if (r == 0) {
                // 記錄 firstReader 為當前線程. 
                firstReader = current;
                // 持有的讀鎖數量為1
                firstReaderHoldCount = 1;

            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null ||
                        rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }

            // return 1, 表示獲取到了1個鎖.
            return 1;
        }
        return fullTryAcquireShared(current);
    }

然後點擊這個按鈕`放行`:

技術分享圖片

就發現控制臺輸出了 "funcA獲取了讀鎖" :

技術分享圖片

3. 獲取第二個讀作

咱們在funcB函數的這句話上也打個斷點:

技術分享圖片

然後再控制臺輸入 "lock b", 然後按下回車:

技術分享圖片

按下回車後, 就發現代碼阻塞在了剛才的斷點上面(紅色行變為了綠色):

技術分享圖片

然後咱們開始分析線程b是如果獲取讀鎖的(記住剛才a線程的讀鎖還沒釋放呢), 按下F7, 進入到lock()的源代碼:

技術分享圖片

再F7,

技術分享圖片

再F7, 終於到了關鍵的地方:

    protected final int tryAcquireShared(int unused) {// 參數沒用
        // 獲取當前線程的引用
        Thread current = Thread.currentThread();
        // 獲取鎖的狀態. c 的低 16 位值,代表寫鎖的狀態. 高16位代表讀鎖的狀態
        int c = getState();

        // exclusiveCount(c) 是寫鎖的state. 不等於 0,說明有線程持有寫鎖 (當前場景下肯定等於0, 所以跳過這段if語句)
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;

        // 讀鎖的state, 由於剛才a線程獲取到了讀鎖, 所以這個計數器現在的值是1
        int r = sharedCount(c);

        // 讀鎖獲取是否應該被阻塞, 其實就是根據`等待隊列`來判斷是否應該被阻塞的 ( 當前場景下沒有比當前線程等待更久的線程, 所以不會被阻塞.)
        if (!readerShouldBlock() &&
                //  判斷是否會溢出 (2^16-1). (當前的r==1, 所以沒有溢出)
                r < MAX_COUNT &&
                // 下面這行 CAS 是將 state 屬性的高 16 位加 1,低 16 位不變,如果成功就代表獲取到了讀鎖 (當前場景下, 沒有線程競爭, 所以肯定成功.)
                compareAndSetState(c, c + SHARED_UNIT)) {

            /* ----------------------
             *  進到這裏就是獲取到了讀鎖
             * ----------------------*/

            // 當前場景下 r == 1, 而且也不是讀鎖重入. 所以執行else語句
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                // cachedHoldCounter 用於緩存最後一個獲取讀鎖的線程 (當前場景下, cachedHoldCounter並沒有被賦值過, 所以是null)
                HoldCounter rh = cachedHoldCounter;

                // 當前場景下cachedHoldCounter為空, 所以進入到這個if語句中.
                if (rh == null ||
                        rh.tid != getThreadId(current))
                    // 利用threadlocal進行創建, 並返回給cachedHoldCounter 和 rh
                    cachedHoldCounter = rh = readHolds.get();

                // 本場景下不執行這個else if, 跳過.
                else if (rh.count == 0)
                    readHolds.set(rh);
                
                // 本場景下, rh剛剛被初始化, 裏面的count肯定是0, 在這裏進行自增操作, 之後就變為了1.
                rh.count++;
            }
            // return 1, 表示本次tryAcquireShared獲取到了1個鎖
            return 1;
        }
        return fullTryAcquireShared(current);
    }

咱們來總結一下這一段代碼都幹什麽了吧. 首先通過cas操作, 將讀鎖的state計數器加了1, (也就是變為了2). 然後就是通過ThreadLocal.get() 方法, 在threadlocal裏創建了一個b線程的計數器, 並且把這個計數器置為1. 然後就沒了....(代碼看起來很多的樣子, 但是實際上沒幹多少事情...)

然後將斷點放行.(從此以後就不詳細講調試過程了. 就只用語言表述了.)

4. 釋放第一個讀鎖

回到咱們的例子Main方法.在funcA函數的unlock()那一行打上斷點. 然後再控制臺輸入 "unlock a", 然後回車:

技術分享圖片

然後咱們就可以開始分析 線程a 釋放讀鎖的過程了, 按F7進入到unlock()函數內部:

技術分享圖片

再F7 :

技術分享圖片

咱們先看看tryReleaseShared方法吧:

protected final boolean tryReleaseShared(int unused) {// 參數沒用
        // 獲取當前線程的引用
        Thread current = Thread.currentThread();

        // 判斷當前線程是不是當前讀鎖中的第一個讀線程, (線程a就是第一個獲取到讀鎖的, 所以滿足這個if條件.)
        if (firstReader == current) {
            assert firstReaderHoldCount > 0;

            // 當前場景下等於 1,所以這次解鎖後, 當前線程就不會再持有鎖了,把 firstReader 置為 null,給後來的線程用
            if (firstReaderHoldCount == 1)
                // 為什麽不順便設置 firstReaderHoldCount = 0?因為沒必要,其他線程使用的時候自己會設值
                firstReader = null;

            // 不會執行這個else.
            else
                firstReaderHoldCount--;

        // 不會執行這個else語句. 跳過.
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null
                    || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            if (count <= 1) {
                readHolds.remove();
                if (count <= 0) throw unmatchedUnlockException();
            }
            --rh.count;
        }

        for (; ; ) {
            int c = getState();
            // state 的高 16 部分位減 1 , 低16位不動. (高16位是共享模式)
            // 高16位的部分, 現在是2. 在這一步減去了1, 所以執行完下面這行代碼後 nextc == 1
            int nextc = c - SHARED_UNIT;
            // cas 更新 state的值為nextc, (當前場景下也就是 1 了), 當前場景下沒有爭搶, cas肯定成功. 
            if (compareAndSetState(c, nextc))
                // 釋放讀鎖, 對讀線程們沒有什麽影響
                // 但如果是 nextc == 0,那就是 state 全部 32 位都為 0,也就是讀鎖和寫鎖都空了
                // 此時這裏返回 true 的話,其實是幫助喚醒後繼節點中的獲取寫鎖的線程
                // 當前場景下, nextc等於1.所以返回false.
                return nextc == 0;
        }
    }

這段代碼最終返回了false, 然後回到上一層函數. 由於返回了false, 所以不會進入到if語句裏, 也就是不會執行doReleaseShared()方法:

技術分享圖片

然後點擊`放行`按鈕. funcA的讀鎖釋放過程就到此結束了.

5. 釋放第二個讀鎖

回到Main方法. 咱們在funcB裏的unlock()函數那一行打上斷點. 在控制臺輸入"unlock b", 然後回車.

技術分享圖片

然後調試, 一直進入到tryReleaseShared()方法. 剛才講了tryReleaseShared釋放線程a持有的讀鎖的步驟. 咱們現在看看線程b執行這段代碼會有什麽不同吧:

    protected final boolean tryReleaseShared(int unused) {// 參數沒用
        // 獲取當前線程的引用
        Thread current = Thread.currentThread();

        // 判斷當前線程是不是當前讀鎖中的第一個讀線程,(本場景中, 當然不是了, 而且剛才釋放a的讀鎖的時候, firstReader被設置為了null, 所以也不滿足if. 就是說不管讀鎖a之前是否釋放了, 這裏都不會滿足if條件)
        if (firstReader == current) {
            assert firstReaderHoldCount > 0;
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;

        // 會執行這個else語句, 而不是上面的if語句.
        } else {
            HoldCounter rh = cachedHoldCounter;
            // 判斷cachedHoldCounter是不是空, 當前場景下cachedHoldCounter不是空, 所以跳過這個if語句.
            if (rh == null
                    || rh.tid != getThreadId(current))
                rh = readHolds.get();
            
            // 獲取cachedHoldCounter的計數器, 當前是 1
            int count = rh.count;
            
            // 如果計數器小於等於1, 說明該釋放了.(目前滿足這個if條件, 所以會執行if代碼塊)
            if (count <= 1) {
                // 這一步將 ThreadLocal中當前線程對應的計數器 remove 掉,防止內存泄漏。因為已經不再持有讀鎖了
                readHolds.remove();
                // 沒鎖還要釋放? 給你拋個異常...
                if (count <= 0) throw unmatchedUnlockException();
            }
            // 計數器 減 1
            --rh.count;
        }
        for (; ; ) {
            int c = getState();
            // state 的高 16 部分位減 1 , 低16位不動. (高16位是共享模式), 執行完下面這行的減1操作後, nextc就變為0了.
            int nextc = c - SHARED_UNIT;
            // cas 設置 state
            if (compareAndSetState(c, nextc))
                // 釋放讀鎖, 對讀線程們沒有什麽影響
                // 但如果是 nextc == 0,那就是 state 全部 32 位都為 0,也就是讀鎖和寫鎖都空了
                // 此時這裏返回 true 的話,其實是幫助喚醒後繼節點中的獲取寫鎖的線程
                // 目前nextc是0, 所以會返回true.
                return nextc == 0;
        }
    }

最終本段代碼返回了true, 回到上層代碼, 由於返回了true, 所以會執行if代碼塊裏的doReleaseShared()方法:

技術分享圖片

接下來, 咱們看看doReleaseShared()方法都做了什麽事情吧:

技術分享圖片

由於沒有線程進入過`等待隊列`, 所以等待隊列的head還是null, 所以直接就break了, 什麽都沒幹.

本小節的demo, 就到此結束了.

三. 公平讀/寫鎖的申請和釋放

場景如下: 線程a獲取讀鎖 -> 線程b獲取讀鎖 -> 線程a獲取寫鎖 -> 線程a釋放寫鎖 -> 線程a釋放讀鎖 -> 線程b釋放讀鎖.

1. 場景demo: (還是那句話, 想運行我程序的, 把MyReentrantReadWriteLock 改為JDK的 ReentrantReadWriteLock 就好了. My*系列的都是我復制的JDK代碼, 然後改了個名字而已)

import java.util.Scanner;
import java.util.function.Supplier;

public class Main {
    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) {
        new Thread(Main::funcA).start();
        new Thread(Main::funcA2).start();
        new Thread(Main::funcB).start();

        while (scanner.hasNext()) {
            cmd = scanner.nextLine();
        }
    }

    public static void funcA() {
        blockUntilEquals(() -> cmd, "lock read a");
        readLock.lock();
        System.out.println("funcA獲取了讀鎖");
        blockUntilEquals(() -> cmd, "unlock read a");
        readLock.unlock();
        System.out.println("funcA釋放了讀鎖");
    }
    
    public static void funcA2(){
        blockUntilEquals(() -> cmd, "lock write a");
        writeLock.lock();
        System.out.println("funcA獲取了寫鎖");
        blockUntilEquals(() -> cmd, "unlock write a");
        writeLock.unlock();
        System.out.println("funcA釋放了寫鎖");
    }

    public static void funcB() {
        blockUntilEquals(() -> cmd, "lock read b");
        readLock.lock();
        System.out.println("funcB獲取了讀鎖");
        blockUntilEquals(() -> cmd, "unlock read b");
        readLock.unlock();
        System.out.println("funcB釋放了讀鎖");
    }

    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();
        }
    }
}

2. 獲取讀鎖

首先是a獲取讀鎖, 接下來是b獲取讀鎖. 這個場景在上小節中將讀鎖的時候已經講過了. 所以這裏一代而過.

運行上面這個場景demo, 然後按下進行輸入, 來讓a線程獲取讀鎖, 然後讓b線程獲取讀鎖:

技術分享圖片

3. 獲取寫鎖

把斷點打在writeLock.lock()方法上, 然後輸入"lock write a", 按下回車, 來讓a線程獲取寫鎖:

技術分享圖片

發現線程阻塞在了writeLock.lock()方法上. 咱們開始一遍調試一遍分析代碼.

F7, 進入到了ReentrantReadWriteLock.WriteLock類裏的lock()方法:

技術分享圖片

接下來就很熟悉了, 根ReentrantLock裏的申請鎖是同一段代碼:

技術分享圖片

但還不是完全一樣, 因為ReadWriteLock重寫了其中的tryAcquire方法:

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        // 獲取寫鎖的重入次數, w 在本場景中等於0
        int w = exclusiveCount(c);
        // c==0說明, 寫鎖和讀鎖都沒有.
        if (c != 0) {
            //   c != 0 && w == 0: 寫鎖可用,但是有線程持有讀鎖(也可能是自己持有) , 在本場景中, 會滿足w==0的條件, 而進入if語句
            if (w == 0 ||
                    current != getExclusiveOwnerThread())
                // 返回true
                return false;

            // ***********************************************************
            // ---- 本場景根下面的代碼沒關系, 因為會在上一行的return中直接返回false.
            // ***********************************************************
            
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");

            setState(c + acquires);
            return true;
        }
        if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }

然後就是執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) . 這句代碼的內部實現與ReentrantLock的代碼一模一樣(就是同一段代碼). 就不再復述了.

執行完這句話之後, 剛剛的申請寫鎖的線程就被掛起了, 等待著讀鎖釋放完了後喚醒他.

咱們知道他是通過調用AQS類裏的parkAndCheckInterrupt方法來進行掛起操作的. 咱們在掛起操作的下一行打個斷點. 這樣, 到時候這個線程被喚醒後, 咱們就可以感知到了:

技術分享圖片

接下來咱們在funcB函數裏的unlock()方法上打個斷點:

技術分享圖片

4. 釋放所有讀鎖, 來讓寫鎖被激活

接下來咱們釋放掉讀鎖a, 然後釋放掉讀鎖b, 然後線程就會在funcB函數裏的unlock()方法上阻塞. (釋放這兩個鎖的流程在前文中已經講過了, 所以下面簡單描述):

技術分享圖片

按F7, 進入函數內部, 一步一步調試, 最終會執行到 doReleaseShared()方法:

技術分享圖片

這裏其實就是在喚醒`等待隊列`裏的第一個寫鎖.

在這裏點擊`放行`. (點擊`放行`就是:"讓剩余的函數自動執行完, 一直執行到下一個斷點")

就會發現跳轉到這裏了: (也就是剛才進入等待隊列的那個申請寫鎖的線程從掛起狀態恢復到了運行狀態)

技術分享圖片

咱們在此點擊`放行`按鈕, 然後這個寫鎖就申請完了:

技術分享圖片

接下來咱們看看寫鎖的釋放過程. 在funcA2函數裏的unlock()方法上打上斷線, 然後再控制臺輸入"unlock write a", 並回車:

技術分享圖片

然後老規矩按F7, 進入函數內部.查看源碼:

技術分享圖片

再進入一層:

技術分享圖片

首先是嘗試釋放鎖, 如果鎖可以完全釋放的話, 就會激活`等待隊列`裏的第一個線程.

咱們看看讀寫鎖的tryRelease方法的內部實現吧:

技術分享圖片

其實就是計數器減1, 然後如果等於0的話, 就返回true.表示鎖釋放幹凈了. 沒有重入.

然後回到上層方法, 由於返回的是true, 所以會進入到if語句裏, 然後去判斷是否還有線程要獲取鎖. 如果有的話就用unparkSuccessor方法喚醒. 如果沒有的話就直接返回true, 然後結束:

技術分享圖片

四.公平讀鎖從等待隊列中喚醒 (未完待續)

咱們在這一小節 分析一下讀鎖進入等待隊列的流程, 和讀鎖在等待隊列中被喚醒的流程.

1. 用於測試本場景的代碼

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 Main {
    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>() {{
            put("r1", readLock);
            put("r2", readLock);
            put("r3", readLock);
            put("w1", writeLock);
            put("w2", writeLock);
            put("w3", writeLock);
        }}.entrySet()) {
            new Thread(() -> func(entry::getValue, entry.getKey())).start();
        }

        // 下面這四行, 等價於上面的for循環.
//        new Thread(() -> func(() -> readLock, "r1")).start();
//        new Thread(() -> func(() -> readLock, "r2")).start();
//        new Thread(() -> func(() -> writeLock, "w1")).start();
//        new Thread(() -> func(() -> writeLock, "w2")).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();
        }
    }
}

2. 上面這段代碼的用法

運行這段代碼後, 按下面這樣進行輸入:

技術分享圖片

首先是有一個線程申請了寫鎖w2, 然後是有兩個線程分別申請了讀鎖 r1 和 r2. 等到w2被釋放的時候, r1 r2 都申請到了鎖.

(輸入的時候, 不要打錯字, 很容易打錯的.)

3. 讀鎖進入`等待隊列`

重新運行這段程序.先輸入"lock write w1" , 先申請寫鎖. 然後在func方法內部的`myLockSupplier.get().lock();`這一行代碼打上斷點. 然後輸入"lock read r1", 申請讀鎖.

(由於先申請了寫鎖, 而且這個寫鎖還沒有釋放. 所以這個時候申請讀鎖就意味著會進入`等待隊列`)

技術分享圖片

然後按下F7, 進入到源代碼中: (ReentrantReadWriteLock的內部類ReadLock類裏的lock()方法)

技術分享圖片

繼續按F7, 進入到方法內部, 咱們就看到了acquireShared方法:

技術分享圖片

由於剛才咱們成功申請了寫鎖, 而且還沒釋放. 所以這次讀鎖肯定申請失敗.

也就是說tryAcquireShared方法嘗試獲取鎖會失敗. 失敗了就會返回-1. tryAcquireShared方法之前講過了, 就不細講了.

tryAcquireShared失敗了, 就會滿足if條件. 然後就會進入到if語句中執行doAcquireShared方法. 咱們繼續往下分析這個doAcquireShared :

    private void doAcquireShared(int arg) {
        // 將節點放入`等待隊列`
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (; ; ) {
                final Node p = node.predecessor();
                // 判斷當前是不是等待隊列中的第一個
                if (p == head) {
                    // 嘗試獲取鎖
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 把自己設置為新的頭部, 然後看看是否需要向後續蔓延
                        // (也就是, 如果是Shared模式, 那麽就會把後續連續的讀鎖線程都喚醒)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

其中主要的是setHeadAndPropagate方法. 咱們進入查看源碼:

技術分享圖片

在這裏將`等待隊列`裏的第一個節點設置為了Head節點. 然後判斷是不是下一個節點是不是共享模式的, 也就是判斷下一個節點是不是共享模式, 如果是的話, 就會執行doReleaseShared()方法. 最終會導致, 一個讀鎖獲取成功的時候, 會帶著其後續連續的讀鎖都一起獲取成功.

其中的doReleaseShared()方法在前面小節已經介紹過了, 就不講了. 如果哪裏遺漏了就後續補充.

[源碼分析]讀寫鎖ReentrantReadWriteLock