Java基礎--ReentrantReadWriterLock--重入讀寫鎖

1. ReentrantReadWriterLock的整體結構

1.1 ReentrantReadWriterLock的UML圖

在這裏插入圖片描述

1.2 ReentrantReadWriterLock的屬性、方法

在這裏插入圖片描述

2. ReentrantReadWriterLock 實現ReadWriterLock接口

既然ReentrantReadWriteLock實現了ReadWriteLock接口,那麼就需要實現ReadWriteLock的方法
在這裏插入圖片描述
不管是readLock還是writeLock返回的都是Lock類型,Lock則必須實現這些方法:
在這裏插入圖片描述

2.1 readLock

在這裏插入圖片描述
readLock方法直接返回局部變量readerLock的值。

2.1.1 tryLock

在這裏插入圖片描述
直接調用Sync的tryReadLock方法,請跳轉3.1.
sync,tryReadLoack的方法總結就是看看當前線程有沒有獲取讀鎖,如果已經獲取了讀鎖,那麼將當前線程的重入層數++,如果當前線程沒有獲取讀鎖,且不能獲取鎖,那麼嘗試獲取讀鎖失敗。否則當前線程是第一次獲取讀鎖,將當前線程加入讀鎖持有線程映射表中。

2.1.2 tryLock(long,TimeUnit)

在這裏插入圖片描述
帶有超時時間的嘗試獲取鎖,則是調用AQS的帶有超時時間的嘗試獲取共享鎖的方法。
這是AQS的帶有超時時間的嘗試獲取共享鎖的方法的時序圖如下:
在這裏插入圖片描述
巨複雜!!!不過,不要怕,我們一步一步看。

// 嘗試獲取共享鎖,有超時時間,響應中斷
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) // arg = 1
        throws InterruptedException {
    // 獲取線程中斷標誌,並重置中斷標誌
    if (Thread.interrupted())
    	// 如果線程已經中斷,那麼直接拋出中斷異常,快速結束
        throw new InterruptedException();
    // 否則就會調用tryAcquireShared 和 doAcquireSharedNanos方法
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}

tryAcquireShared方法請看3.2
doAcquireSharedNanos請看
Java基礎–AQS原理
的5.6.2.4和5.6.5小節

2.1.3 lock

在這裏插入圖片描述
直接調用Sync的acquireShared方法,請見3.2.

2.1.4 lockInterruptibly

阻塞獲取鎖,響應中斷。
在這裏插入圖片描述
直接調用AQS的acquireSharedInterruptibly方法。
AQS的acquireSharedInterruptibly方法請看
Java基礎–AQS原理
的5.6.4 小節

2.1.5 unlock

在這裏插入圖片描述
直接調用AQS的releaseShared方法。

// 釋放共享鎖
public final boolean releaseShared(int arg) {
	// 調用AQS子類實現的方法,嘗試釋放共享鎖
    if (tryReleaseShared(arg)) {
    	// 自旋釋放共享鎖
        doReleaseShared();
        // 返回共享鎖釋放成功
        return true;
    }
    // 釋放共享鎖失敗
    return false;
}

在ReentrantReadWriteLock中,Sync繼承了AQS。
在這裏插入圖片描述
Sync的tryReleaseShared請見3.4

2.1.6 newCondition

在這裏插入圖片描述
讀鎖不允許使用condition,強行使用會直接拋出異常。

2.2 writerLock

在這裏插入圖片描述
writeLock方法直接返回局部變量readerLock的值。

2.2.1 tryLock

在這裏插入圖片描述
調用Sync的tryWriteLock方法,請見3.5

2.2.2 tryLock(long,TimeUnit)

在這裏插入圖片描述
調用AQS的tryAcquireNanos方法
AQS的tryAcquireNanos方法請看
Java基礎–AQS原理
的5.6.5小節
AQS的tryAcquireNanos方法會調用子類實現的tryAcquire實現獨佔鎖的獲取。
在ReentrantReadWriteLock的內部類Sync實現了tryAcquire方法
請見3.6

2.2.3 lock

在這裏插入圖片描述
直接調用AQS的acquire方法
AQS的acquire方法請看
Java基礎–AQS原理
的5.6.2小節
在AQS的acquire方法中會調用tryAcquire方法獲取獨佔鎖(寫鎖)
而ReentrantReadWriteLock中的Sync中實現了tryAcquire方法
請見3.6

2.2.4 lockInterruptibly

在這裏插入圖片描述
調用AQS的acquireInterruptibly方法。
AQS的acquireInterruptibly方法請看
Java基礎–AQS原理
的5.6.4 小節

2.2.5 unlock

在這裏插入圖片描述
調用AQS的release方法
AQS的release方法請看
Java基礎–AQS原理
的5.6.3 小節
AQS的release方法會調用子類實現的tryRelease方法釋放獨佔鎖(寫鎖)
ReentrantReadWriteLock中的Sync實現了tryRelease方法,
請見3.7

2.2.6 newCondition

在這裏插入圖片描述
調用ReentrantReadWriteLock的Sync的newCondition方法
在這裏插入圖片描述
ReentrantReadWriteLock的Sync的newCondition方法直接返回AQS的ConditionObject對象。
請看
AQS的Condition源碼解析

3. ReentrantReadWriterLock 內部類Sync繼承了AQS

Sync繼承了AQS:
在這裏插入圖片描述
Sync內部使用了HoldCounter和ThreadLocalHoldCounter
在這裏插入圖片描述

3.1 tryReadLock

這是tryReadLock的時序圖
在這裏插入圖片描述

// 嘗試獲取讀鎖
final boolean tryReadLock() {
	// 首先獲取當前線程
    Thread current = Thread.currentThread();
    // 開始自旋
    for (;;) {
    	// 獲取當前線程節點的狀態(可以理解爲現在持有讀鎖的線程數量,最大65535個線程)
        int c = getState();
        if (exclusiveCount(c) != 0 && // 如果讀鎖不空閒(只有線程節點的等待狀態爲0時,才能嘗試獲取鎖,線程節點的等待狀態爲0表示鎖空閒)
        // 這裏獲取的數量是讀鎖以獨佔模式使用時(其實就是鎖現在是寫鎖)
        	// 如果現在鎖是以寫鎖被使用時,讀鎖現在不可用
        	// 因爲讀鎖每次重入,都會將鎖狀態的值增加65535,所以,上面判斷現在鎖是以寫鎖使用
            getExclusiveOwnerThread() != current) // 當前線程還未持有讀鎖
            // 如果寫鎖持有線程是當前線程,那麼就表示在同一個線程內即獲取寫鎖,又獲取讀鎖
            // 根據串行語義一致性原則,同一個線程既要寫,又要讀,是完全沒有問題的
            // 直接返回獲取讀鎖失敗
            return false;
        // 只有線程節點的等待狀態爲0,表示鎖空閒,才能走到這裏
        int r = sharedCount(c); // 獲取持有讀鎖的線程數量(這裏的數量是讀鎖以共享模式使用時)
        // 上面排除了鎖是寫鎖使用的情況,現在是讀鎖
        // 這裏可以理解爲當前線程獲取的讀鎖的充入層數,因爲讀寫鎖還是可重入鎖
        if (r == MAX_COUNT) // 如果共享讀鎖現在持有鎖的線程數量達到最大值31(鎖現在是讀鎖)
            throw new Error("Maximum lock count exceeded"); // 那麼直接拋出異常快速失敗
        if (compareAndSetState(c, c + SHARED_UNIT)) { // 設置讀鎖的等待狀態是c+65536 ,+65536是爲了記錄重入層數將c+SHARED_UNIT除以SHARED_UNIT就是重入層數
            if (r == 0) { // 如果鎖的線程持有數量等於0,表示當前線程是第一個獲取讀鎖的線程
                firstReader = current; // 設置第一個獲取讀鎖的線程是當前線程
                // 這裏不使用cas也沒有關係,設置firstReader只是偏向哪個線程而已
                firstReaderHoldCount = 1; // 設置第一個獲取讀鎖的重入層數是1
            } else if (firstReader == current) { // 如果第一個獲取讀鎖的線程重入
            	// 這裏是偏向鎖的實現,第一個獲取讀鎖的線程,讀鎖釋放後,再次獲取讀鎖,不會加入線程映射表,不需要創建HoldCounter和ThreadLocalHoldCounter對象,會比較快
                firstReaderHoldCount++; // 重入層數++
            } else {
            	// 當前線程不是第一個獲取讀鎖的線程時,需要創建映射表
                HoldCounter rh = cachedHoldCounter; // 獲取緩存的線程重入數量對象
                // HoldCounter是記錄線程重入層數的,cachedHoldCounter是緩存對象
                if (rh == null || rh.tid != getThreadId(current)) // 如果讀鎖持有線程爲空或者讀鎖持有線程不是當前線程
                    cachedHoldCounter = rh = readHolds.get(); // 獲取當前線程的重入數量對象,重入量對象是一個線程變量
                else if (rh.count == 0) // 第一次進來,當前線程讀鎖重入次數爲0
                	// 將當前線程的重入數量對象初始化到線程變量
                    readHolds.set(rh);
                // 每次獲取讀鎖,都應該將重入層數++
                rh.count++;
            }
            // 返回獲取讀鎖成功
            return true;
        }
    }
}

在這裏插入圖片描述
傳入數量除以每一個獨佔線程的間隔進行與操作。可以理解爲,返回c/65535
爲什麼需要這樣處理呢?
因爲在ReentrantReadWriteLock中state的高16位表示讀鎖,低16位表示寫鎖。
在這裏插入圖片描述
SHARED_SHIFT的值是16,那麼EXCLUSIVE_MASK的值是65535
在這裏插入圖片描述
獲取持有鎖的線程。

3.2 tryAcquireShared

嘗試獲取共享鎖
時序圖如下:
在這裏插入圖片描述

// 嘗試獲取共享鎖(讀鎖)
protected final int tryAcquireShared(int unused) {
	// 獲取當前線程
    Thread current = Thread.currentThread();
    // 獲取鎖狀態
    // 在AQS中有一個state,表示鎖狀態,state=0表示鎖空閒,state>0表示鎖佔用
    // 在AQS中有兩個隊列:競爭等待隊列,由AQS對象的head和tail引用;等待通知隊列,由AQS內部類ConditionObject對象的fristWaiter和lastWaiter引用
    // AQS中兩個隊列的元素節點都是AQS內部的Node的實例對象
    // Node中的waitStatus是線程節點的等待狀態(0:初始化;1:取消;-1:SIGNAL;-2:CONDITION;-3:PROPAGATE)
    int c = getState();
    if (exclusiveCount(c) != 0 && // 如果鎖被以獨佔的方式持有(寫鎖),那麼共享鎖(讀鎖)不能獲取
        getExclusiveOwnerThread() != current) // 如果佔有鎖的線程不是當前線程(如果是當前線程表示寫鎖重入)
        // 返回嘗試獲取共享鎖失敗
        return -1;
    // 獲取共享鎖持有線程數(每有一個新線程進入,state就會加65536)
    int r = sharedCount(c); // c >>> 16 就等價於 c/65536
    if (!readerShouldBlock() && // 如果當前線程前面沒有等待線程,那麼當前線程就可以獲取鎖了,否則應該先處理當前線程前面的線程
        r < MAX_COUNT && // 共享鎖持有線程數量沒有超過最大值65535
        compareAndSetState(c, c + SHARED_UNIT)) { // 鎖狀態+65535
        // 如果鎖持有線程數量等於0,表示當前線程是第一個獲取鎖的線程
        if (r == 0) { // 當前線程是第一個獲取鎖的線程
            firstReader = current; // 設置第一個獲取讀鎖的線程是當前線程(這裏是偏向鎖,如果是第一個線程再次獲取鎖,那麼會進行比較,相等會快速獲得鎖)
            firstReaderHoldCount = 1; // 第一個獲取到讀鎖的線程第一次獲取到讀鎖,這裏理解是第一個獲取到讀鎖的線程的重入層數是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) // 如果重入層數是0,表示是完全釋放讀鎖後,再次進入,此時緩存的重入數量對象是當前線程的重入數量對象
            	// 將緩存的重入數量對象設置爲線程變量(爲什麼?存疑,應該與釋放有關,釋放後能會清理線程變量)
                readHolds.set(rh);
            // 重入層數++
            rh.count++;
        }
        // 返回獲取共享鎖成功
        return 1;
    }
    return fullTryAcquireShared(current);
}

3.3 fullTryAcquireShared

噹噹前線程前面還有等待讀取的線程的時候,需要先處理前面的讀線程,然後在處理後面的線程。
自旋獲取共享鎖
這是自旋獲取共享鎖的時序圖:
在這裏插入圖片描述

// 自旋獲取共享鎖(讀鎖)
final int fullTryAcquireShared(Thread current) {
	// 新建當前線程的重入數量對象
    HoldCounter rh = null;
    // 開始自旋
    for (;;) {
    	// 獲取鎖狀態
        int c = getState();
        // 當前鎖是讀鎖還是寫鎖
        if (exclusiveCount(c) != 0) { // 獲取鎖狀態的低16位
        	// 如果是寫鎖,那麼判斷當前線程是否已經獲取了寫鎖
            if (getExclusiveOwnerThread() != current)
            	// 如果當前線程沒有獲取寫鎖,而且鎖還是寫鎖狀態時,此時不允許獲取讀鎖
            	// 因爲寫鎖是獨佔鎖
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        // 如果現在鎖狀態是讀鎖,那麼判斷當前線程前面還有沒有等待的讀取線程(讀鎖獲取是公平鎖,不允許插隊)
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            // 如果當前線程是第一個獲取讀鎖的線程
            if (firstReader == current) {
            	// 當前線程已經獲取了讀鎖了,現在是重入,所以只需要將重入數量++即可
                // assert firstReaderHoldCount > 0;
            } else {
            	// 當前線程不是第一個獲取讀鎖的線程,那麼需要初始化線程重入數量計數器
            	// 如果線程重入計數器爲空,那麼需要初始化
                if (rh == null) {
                	// 獲取讀鎖緩存的線程重入數量計數器
                    rh = cachedHoldCounter;
                    // 如果緩存的計數器爲空或者不是當前線程的計數器
                    if (rh == null || rh.tid != getThreadId(current)) {
                    	// 那麼調用get方法獲取計數器(如果不存在會初始化一個0的計數器(不是空對象))
                        rh = readHolds.get();
                        // 如果線程重入次數是0,那麼將計數器從線程變量中移除,只放到緩存變量中
                        if (rh.count == 0)
                        	// 移除線程變量(線程id可能重複)
                            readHolds.remove();
                    }
                }
                // 如果線程重入計數器的數量爲0,那麼直接返回獲取讀鎖失敗
                if (rh.count == 0)
                    return -1;
                // 首先,當前線程前面還有等待的讀取線程纔會走到這裏
                // 其次,前面的讀取線程會初始化緩存計數器
            }
        }
        // 獲取讀鎖持有的線程數量
        // 如果讀鎖的持有線程數量達到最大值65535,那麼拋出錯誤
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // cas 增加讀鎖持有的線程數量
        if (compareAndSetState(c, c + SHARED_UNIT)) {
        	// 如果讀鎖的線程持有數量爲1
            if (sharedCount(c) == 0) {
            	// 那麼將當前線程設置爲讀鎖緩存線程
                firstReader = current;
                // 將讀鎖緩存線程重入數量設置爲1
                firstReaderHoldCount = 1;
            // 如果讀鎖緩存線程和當前線程相等
            } else if (firstReader == current) {
            	// 那麼將讀鎖緩存線程的重入層數++
                firstReaderHoldCount++;
            } else {
            // 如果當前線程與讀鎖緩存線程不是同一個線程(表示現在最少有2個線程同時持有讀鎖)
            	// 如果緩存線程重入數量計數器爲空
                if (rh == null)
                	// 那麼獲取緩存數據(第一次自旋)
                    rh = cachedHoldCounter;
                // 如果緩存線程重入數量計數器爲空或者不是當前線程的重入數量計數器
                if (rh == null || rh.tid != getThreadId(current))
                	// 初始化線程重入數量計數器
                    rh = readHolds.get();
                // 如果重入層數爲0,表示是第一次獲取鎖
                else if (rh.count == 0)
                	// 將線程重入數量計數器放到線程變量中(在前面會將歷史的計數器清空)
                    readHolds.set(rh);
                // 線程重入計數器數值++
                rh.count++;
                // 設置緩存的線程重入計數器是當前線程的計數器
                cachedHoldCounter = rh; // cache for release
            }
            // 返回自旋獲取讀鎖成功
            return 1;
        }
    }
}

3.4 tryReleaseShared

嘗試釋放共享鎖(讀鎖)
這是tryReleaseShared的時序圖
在這裏插入圖片描述

// 嘗試釋放共享鎖(讀鎖)
protected final boolean tryReleaseShared(int unused) { // 傳入的unused=1
	// 獲取當前線程
    Thread current = Thread.currentThread();
    // 如果當前線程是第一個獲取讀鎖的線程(偏向線程)
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        // 當前線程讀鎖重入層數爲1
        if (firstReaderHoldCount == 1)
        	// 清空偏向線程
        	// 第一個線程獲取了一次鎖,然後現在釋放了,所以將偏向鎖清空
            firstReader = null;
        else
        // 如果第一個獲取讀鎖的線程的重入層數不爲1,那麼每次釋放,將重入層數減小1
            firstReaderHoldCount--;
    } else {
    // 當前線程不是第一個獲取讀鎖的線程
    	// 獲取讀鎖重入數量緩存
        HoldCounter rh = cachedHoldCounter;
        // 讀鎖重入數量緩存爲空,或者讀鎖重入數量緩存不是當前線程的緩存
        if (rh == null || rh.tid != getThreadId(current))
        	// 獲取當前線程的讀鎖重入數量緩存
            rh = readHolds.get();
        // 得到當前線程讀鎖的重入層數
        int count = rh.count;
        if (count <= 1) { // 如果重入層數小於等於1,那麼移除當前線程重入數量線程變量
        	// 當前線程如果重入,那麼在重入時,如果線程重入數量的線程變量爲空,會進行新建
            readHolds.remove();
            // 如果重入層數小於等於0,拋出異常(重入層數等於0表示當前線程不持有讀鎖,還去釋放,一定報錯了)
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        // 釋放完成,需要將重入層數--
        --rh.count;
    }
    // 進入自旋,在自旋中保證修改state成功
    for (;;) {
    	// 獲取state,鎖狀態
        int c = getState();
        // 讀鎖持有線程數量減1(重入會加,重入釋放也需要減)
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            // 讀鎖是否空閒(是否完全釋放)
            return nextc == 0;
    }
}

3.5 tryWriteLock

嘗試獲取寫鎖。
這是嘗試獲取寫鎖的時序圖
在這裏插入圖片描述

// 嘗試獲取寫鎖
final boolean tryWriteLock() {
	// 獲取當前線程
    Thread current = Thread.currentThread();
    // 獲取鎖狀態
    int c = getState();
    // 判斷鎖是否空閒
    if (c != 0) { // 鎖不空閒
    	// 獲取鎖是否是寫鎖
        int w = exclusiveCount(c); // 取鎖狀態的低16位
        // 如果鎖狀態的低16位爲0,表示鎖現在是讀鎖
        // 如果當前線程不是鎖持有線程
        if (w == 0 || current != getExclusiveOwnerThread())
        	// 那麼直接返回嘗試獲取寫鎖失敗
            return false;
        // 如果鎖持有線程數量達到最大值,那麼直接拋出錯誤
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    // 鎖持有數量加1(寫鎖低位)
    if (!compareAndSetState(c, c + 1))
    	// 如果cas 設置鎖狀態失敗,那麼嘗試獲取寫鎖失敗
        return false;
    // 否則將當前線程設置爲鎖持有線程
    setExclusiveOwnerThread(current);
    // 返回嘗試獲取寫鎖成功
    return true;
}

3.6 tryAcquire

這是tryAcquire的時序圖:
在這裏插入圖片描述

// 嘗試獲取獨佔鎖
protected final boolean tryAcquire(int acquires) {
	// 獲取當前線程
    Thread current = Thread.currentThread();
    // 獲取鎖狀態
    int c = getState();
    // 獲取鎖狀態的低16位
    int w = exclusiveCount(c);
    // 判斷鎖是不是空閒
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // 鎖不空閒
        // 持有鎖的線程不是當前線程
        if (w == 0 || current != getExclusiveOwnerThread())
        	// 嘗試獲取獨佔鎖(寫鎖)失敗
            return false;
        // 鎖持有的線程數量是否超過寫鎖可以持有的最大線程數量65535
        if (w + exclusiveCount(acquires) > MAX_COUNT)
        	// 如果鎖持有的線程數量超過最大值,那麼直接拋出錯誤
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 設置鎖狀態
        setState(c + acquires);
        // 返回嘗試獲取獨佔鎖(寫鎖)成功
        return true;
    }
    // 如果鎖不空閒,那麼判斷當前線程前面是否還有等待的寫線程
    if (writerShouldBlock() || // 如果當前線程之前還有等待的寫線程(是否可以插隊)
        !compareAndSetState(c, c + acquires)) // 那麼嘗試使用cas 設置失敗
        // 返回嘗試獲取獨佔鎖(寫鎖)失敗
        return false;
    // 設置鎖持有線程是當前線程
    setExclusiveOwnerThread(current);
    // 返回嘗試獲取獨佔鎖(寫鎖)成功
    return true;
}

3.7 tryRelease

tryRelease是嘗試釋放獨佔鎖的方法
這是嘗試釋放獨佔鎖的方法的時序圖
在這裏插入圖片描述

// 嘗試釋放獨佔鎖(寫鎖)
protected final boolean tryRelease(int releases) {
	// 判斷寫鎖持有線程是不是當前線程
    if (!isHeldExclusively())
    	// 如果寫鎖持有的線程不是當前線程,那麼當前線程嘗試釋放自己沒有獲取的獨佔鎖,肯定不對啦
    	// 直接拋出異常,快速失敗
        throw new IllegalMonitorStateException();
    // 獲取鎖狀態釋放後的鎖狀態
    int nextc = getState() - releases;
    // 取得鎖狀態的低16位,即寫鎖鎖重入的層數
    // 如果寫鎖鎖重入的層數等於0,表示當前線程已經完全釋放持有的寫鎖了
    boolean free = exclusiveCount(nextc) == 0;
    // 如果寫鎖釋放完全
    if (free)
    	// 那麼將鎖持有的線程清空
        setExclusiveOwnerThread(null);
    // 否則只是將鎖重入層數減1
    setState(nextc);
    // 返回鎖是否完全釋放
    return free;
}

4. 繼承於Sync的FairSync

在這裏插入圖片描述
直接調用AQS的hasQueuedPredecessors方法
獲取當前線程在等待競爭隊列中有沒有前繼節點。
簡單來說,就是獲取當前線程前面還有沒有等待線程。
在這裏插入圖片描述
如果等待競爭隊列不爲空,那麼頭結點的後繼節點爲空或者等待線程不是當前現場,那麼就表示當前線程前面還有等待的線程。
(不會存在head != tail && head.next == null)

5. 繼承於Sync的NonfairSync

在這裏插入圖片描述
對於非公平鎖,寫鎖直接返回false,讀鎖則調用apparentlyFirstQueuedIsExclusive方法
在這裏插入圖片描述
apparentlyFirstQueuedIsExclusive是判斷等待競爭隊列中的等待線程是不是共享模式。

6. ReadLock

在這裏插入圖片描述
讀鎖內部冗餘持有ReentrantReadWriteLock的Sync對象實例。
讀鎖將Lock全部的方法代理到了ReentrantReadWriteLock內部的Sync的方法中。

7. WriteLock

在這裏插入圖片描述
寫鎖和讀鎖實現完全相同。

8. HoldCounter

線程重入數量計數器
兩個屬性:線程id和重入層數。
在這裏插入圖片描述
HoldCounter很簡單,記錄線程重入層數,並且調用getThreadId方法獲取當前線程的線程id
在這裏插入圖片描述
使用的是UNSAFE的方法獲取線程id.

9. ThreadLocalHoldCounter

線程變量。
在這裏插入圖片描述
線程變量,存儲的是線程重入數量計數器。

10. ReentrantReadWriteLock的構造

在這裏插入圖片描述
因爲ReentrantReadWriteLock內部也實現了AQS的Sync類,而且也基於Sync實現了FairSync和NonfairSync,所以,構造方法就是決定,ReentrantReadWriteLock使用何種方式實現鎖。

10.1 ReentrantReadWriteLock的無參構造

在這裏插入圖片描述
無參構造方法直接調用有參構造,傳入false

10.2 ReentrantReadWriteLock的有參數構造

在這裏插入圖片描述
有參構造的參數是一個boolean參數,boolean參數決定使用FairSync還是NonfairSync。

然後傳入自己,創建ReadLock和WriteLock.

11. 鎖升級與降級

什麼是鎖升級?
對於ReentrantReadWriteLock來說,在某一時刻,他要麼是讀鎖,要麼是寫鎖,要麼空閒。
不能即是讀鎖,又是寫鎖。
對於讀鎖來說,鎖是共享鎖,可以允許多個線程同時持有。
而寫鎖是獨佔鎖,不允許多個線程同時持有。

對於既有讀取,又有寫入的線程來說,線程需要在讀取的時候持有讀鎖,在寫入的時候持有寫鎖。
而鎖的獲取需要和其他線程進行競爭,所以很多時候持有寫鎖,在獨佔鎖的模式下進行線程內讀取和寫入。
這樣做是沒有問題的,但是這樣做對於鎖的資源利用很低。
假設線程內90%的時間都是在讀取,只有10%的時間在寫入,而且寫入在前,那麼此時後90%的時間都是使用獨佔鎖在讀取數據。而對於其他需要讀取的線程來說,此時因爲寫鎖是獨佔鎖,所以,其他讀線程也無法讀取數據。
爲了提高資源利用率,提出了鎖降級。
鎖降級是指:當寫鎖寫入數據完成後,將寫鎖變爲讀鎖,此時其他讀取的線程也能讀取了,這樣就提高了資源利用率。
不過需要注意,只能鎖降級,不能鎖升級。

11.1 鎖降級

鎖降級是在線程持有寫鎖的情況下,獲取讀鎖,獲取到讀鎖後,釋放寫鎖。此時鎖就從寫鎖降級爲讀鎖。
在持有寫鎖的時候,獲取讀鎖:
在這裏插入圖片描述
在同時持有讀鎖和寫鎖的時候釋放寫鎖:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

11.2 鎖升級

鎖升級是指,在持有讀鎖的時候,獲取寫鎖。
這是不正確的,因爲鎖降級是獨佔鎖轉爲共享鎖。
而鎖升級是由共享鎖轉爲獨佔鎖。
鎖升級首先就要求其他線程放棄共享鎖,這幾乎是不可能的。
因爲線程間不應該有依賴關係,線程間應該獨立的。
而且要求其他線程放棄共享鎖也無法實現。所以ReentrantReadWriteLock不支持鎖升級。
不支持鎖升級:
在持有讀鎖的時候,獲取寫鎖:
在這裏插入圖片描述

在鎖降級中,當前線程是鎖持有線程就能獲取鎖。
在鎖升級中,鎖持有線程是空的。

12. 總結

ReentrantReadWriteLock內部持有兩個Lock對象,分別是讀鎖和寫鎖。
ReentrantReadWriteLock的鎖狀態用高16位表示讀鎖,低16位表示寫鎖。鎖持有線程最大是65535.
ReentrantReadWriteLock的讀鎖是偏向鎖,當同時有多個讀線程同時持有鎖,第一個線程會被記錄,而不用加入到線程變量中。這也是偏向鎖的體現。
ReentrantReadWriteLock的讀鎖同時也是樂觀鎖,樂觀認爲大多數情況下只有一個線程獲取讀鎖,所以使用了偏向鎖提高性能。
ReentrantReadWriteLock的讀鎖使用的是共享鎖。
ReentrantReadWriteLock的寫鎖使用的是獨佔鎖。
ReentrantReadWriteLock的寫鎖可以降級爲讀鎖,不能讀鎖升級爲寫鎖。
ReentrantReadWriteLock的鎖降級是獨佔鎖轉爲共享鎖。
ReentrantReadWriteLock內部也有FairSync和NonfairSync實現公平和不公平方式獲取鎖。
對於讀鎖,是否是公平的?
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
對於公平鎖,只要當前線程前面還存在等待讀的線程,那麼就需要阻塞。
換句話說,公平模式下的讀鎖不允許插隊。
不公平模式
在這裏插入圖片描述
只要鎖是共享鎖,那麼就不需要阻塞,也就是可以插隊。

對於寫鎖:
在這裏插入圖片描述
公平模式:
在這裏插入圖片描述
寫鎖公平模式下不允許插隊。
不公平模式:
在這裏插入圖片描述
寫鎖不公平模式下一直允許插隊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章