Java基礎--ReentrantLock--重入鎖

1. ReentrantLock的整體結構

1.1 ReentrantLock的UML圖

在這裏插入圖片描述
感覺有些抽象,稍微調整下佈局:
在這裏插入圖片描述
這是一個巨複雜的類。
不過,仔細看,ReentrantLock上面直接連接的關係並不多:ReentrantLock實現了Lock接口;其擁有一個內部類Sync,Sync類繼承於AQS;同時ReentrantLock內部繼承Sync實現了NonfairSync和FairSync。
也就是說,ReentrantLock其實可以分爲三部分:
第一部分,ReentrantLock實現了Lock方法,所以需要實現Lock的接口;
第二部分,ReentrantLock內部的Sync繼承於AQS,在一定程度上實現了AQS要求實現的方法;
第三部分,ReentrantLock內部的NonfairSync和FairSync繼承內部的Sync,增強了Sync的實現。

1.2 ReentrantLock的屬性、方法

在這裏插入圖片描述

2. ReentrantLock 實現Lock接口

既然ReentrantLock實現了Lock接口,那麼,我們就從Lock入手。
在這裏插入圖片描述

2.1 tryLock

在這裏插入圖片描述
可以看到,ReentrantLock的tryLock方法只是內部Sync的nonfairTryAcquire的代理,請從目錄跳轉到3.2.
這裏傳入的1表示每次重入,鎖的重入層數加1.

2.2 tryLock(long,TimeUnit)

在這裏插入圖片描述
這個方法也是做了代理。代理的目標方法是AQS的方法:
在這裏插入圖片描述
tryAcquireNanos的方法的時序圖如下:
在這裏插入圖片描述
首先是調用ReentrantLock中FairSync和NonfairSync實現的tryAcquire方法。
如果tryAcquire方法失敗,就會調用doAcquireNanos方法。
tryAcquire方法請跳轉4.2,4.4

// 自旋指定納秒獲取鎖,響應中斷
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    // 如果等待的納秒小於等於0,那麼直接失敗
    if (nanosTimeout <= 0L)
        return false;
    // 根據剛進入方法時,系統的納秒時間和等待時間計算出,截止時間
    final long deadline = System.nanoTime() + nanosTimeout;
    // 將當前線程以獨佔的方式加入等待競爭隊列
    final Node node = addWaiter(Node.EXCLUSIVE);
    // 定義獲取鎖失敗狀態的標誌
    boolean failed = true;
    try {
    	// 進入自旋
        for (;;) {
        	// 獲取node的前繼線程節點
            final Node p = node.predecessor();
            // 如果node的前繼線程節點是頭節點(表示node是第一個等待的線程)
            // tryAcquire嘗試獲取鎖成功
            if (p == head && tryAcquire(arg)) {
            	// 將node設置爲頭結點(設置頭結點會將線程節點的線程和前繼清空)
                setHead(node);
                // 將原頭節點從等待競爭隊中移除
                p.next = null; // help GC
                // 設置獲取鎖成功
                failed = false;
                // 返回獲取鎖成功
                return true;
            }
            // 每次自旋都進行判斷剩餘自旋時間
            nanosTimeout = deadline - System.nanoTime();
            // 如果剩餘自旋時間小於等於0,那麼需要結束自旋
            if (nanosTimeout <= 0L)
            	// 結束自旋的時候還沒有獲取到鎖,所以獲取鎖失敗,返回獲取鎖失敗
                return false;
            // 調用shouldParkAfterFailedAcquire方法,將node前繼節點的等待狀態設置爲SIGNAL
            if (shouldParkAfterFailedAcquire(p, node) &&
            	// 如果剩餘自旋時間大於1000納秒,那麼阻塞線程,否則長時間自旋耗費CPU資源
                nanosTimeout > spinForTimeoutThreshold)
                // 阻塞線程指定納秒數
                LockSupport.parkNanos(this, nanosTimeout);
            // 獲取線程中斷狀態,並且重置中斷標誌
            if (Thread.interrupted())
            	// 如果線程已經被中斷,那麼直接拋出中斷異常
                throw new InterruptedException();
        }
    } finally {
    	// 如果線程獲取鎖失敗,那麼需要清理等待競爭隊列中,node節點前面的已經獲取鎖的節點
    	// 換句話說就是清理node前面已經獲取到鎖的線程
    	// 同時如果node前面的線程節點的等待狀態是0或者1(已經獲取鎖,但是線程還是阻塞),那麼就喚醒線程
    	// 可以理解爲:喚醒已經獲得鎖或者將要獲得鎖的線程幹活
        if (failed)
            cancelAcquire(node);
    }
}
private void setHead(Node node) {
    head = node;
    // 清空頭結點的線程
    node.thread = null;
    // 清空頭結點的前繼
    node.prev = null;
}

2.3 lock

在這裏插入圖片描述
ReentrantLock中的lock方法代理指向了ReentrantLock內部實現的Sync類的lock方法,請跳轉到3.1.

2.4 lockInterruptibly

在這裏插入圖片描述
ReentrantLock的lockInterruptibly方法代理指向ReentrantLock內部類Sync的acquireInterruptibliy方法。
在ReentrantLock的內部類Sync中並沒有對AQS的acquireInterruptibly方法做任何修改。所以調用的實際上是AQS的acquireInterruptibly方法。
Java基礎–AQS原理
中的5.6.4小節

2.5 unlock

在這裏插入圖片描述
ReentrantLock的unlock方法,先是調用AQS的release方法,在release方法中調用ReentrantLock內部Sync的tryRelease方法。
首先AQS的release方法請看
Java基礎–AQS原理
的5.6.3小節。
在AQS的release中會調用tryRelease方法,而ReentrantLock的內部類Sync實現了tryRelease方法,請跳轉本文3.3。

2.6 newCondition

在這裏插入圖片描述
newCondition代理指向ReentrantLock內部類的newCondition方法,請跳轉3.5.

2.7 其他方法

在這裏插入圖片描述

3. ReentrantLock 內部Sync實現了AQS

這是ReentrantLock內部Sync的結構:
在這裏插入圖片描述

3.1 lock

在這裏插入圖片描述
在ReentrantLock內部類Sync中的lock方法是抽象方法,要求子類實現的,所以真正的實現是ReentrantLock內部Sync的子類NonfairSync和FairSync的lock方法。
請跳轉4.1,4.3.

3.2 nonfairTryAcquire–不公平的嘗試獲取鎖

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

// 不公平的嘗試獲取鎖,在ReentrantLock的tryLock方法中,傳入的值是1
final boolean nonfairTryAcquire(int acquires) {
	// 獲取當前線程
    final Thread current = Thread.currentThread();
    // 獲取線程節點的等待狀態
    int c = getState();
    // 如果等待狀態等於0,表示鎖空閒
    if (c == 0) {
    	// 設置線程節點的等待狀態是取消(換種理解方向就是線程節點的線程已經獲取到了鎖,不需要再進行競爭了)
        if (compareAndSetState(0, acquires)) {
        	// 設置鎖持有的線程是當前線程(鎖持有線程是AOS的屬性)
            setExclusiveOwnerThread(current);
            // 返回嘗試獲取鎖成功
            return true;
        }
    }
    // 如果等待狀態不等於0,表示鎖已經被線程持有了
    // 如果持有鎖的線程和當前線程相同,表示當前線程已經獲取了鎖,現在正在重入
    else if (current == getExclusiveOwnerThread()) {
    	// 得到重入次數(或者說重入層數)
        int nextc = c + acquires;
        // 重入次數應該大於等於0
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 重入次數加1
        setState(nextc);
        // 可重入,所以返回嘗試獲取鎖成功
        return true;
    }
    // 否則獲取鎖失敗
    return false;
}

3.3 tryRelease

這是Sync的tryRelease方法的時序圖:
在這裏插入圖片描述

// 這裏傳入的值是1,表示每釋放一次,鎖重入層數減小1層
protected final boolean tryRelease(int releases) { // releases = 1
    int c = getState() - releases; // 獲取當前鎖重入層數,減去釋放的層數,得到鎖釋放後的層數
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果不是當前線程持有鎖,那麼鎖釋放直接拋出異常(釋放不屬於自己的鎖(或者自己還未獲取鎖,而是直接越權釋放其他線程的鎖))
        throw new IllegalMonitorStateException();
    // 定義鎖釋放的狀態,初始化爲失敗
    boolean free = false;
    // 如果鎖釋放後重入層數爲0,表示當前線程持有的鎖已經全部釋放,重入層數爲0
    if (c == 0) {
    	// 修改鎖釋放狀態爲釋放
        free = true;
        // 清空鎖持有線程(當前線程已經用完,且完全釋放了鎖)
        setExclusiveOwnerThread(null);
    }
    // 更新線程節點的等待狀態爲0(表示鎖空閒)(此時線程節點是頭結點
    // (頭結點存儲線程,因爲在獨佔模式下,只有頭結點的後繼節點纔是鎖接下來持有的線程;
    // 當擡頭節點的後繼獲取到鎖後,會更新頭結點爲後繼節點))
    // 當頭結點的鎖釋放後,頭結點的後繼節點的線程會自旋,
    // 自旋執行ReentrantLock中NonfairSync和FairSync實現的tryAcquire方法
    setState(c);
    // 返回鎖釋放狀態
    return free;
}

3.4 isHeldExclusively

是否是獨佔模式,只有condition纔會用到
這個方法在ReentrantLock中主要可以判斷當前線程是不是獲取到了鎖:
在這裏插入圖片描述
將當前線程與鎖持有線程進行比較,如果相等,則返回true,表示當前線程已經獲取到了鎖,否則返回false,表示當前線程沒有獲取到鎖。

3.5 newCondition

在這裏插入圖片描述
newCondition的方法很簡單,直接new 一個AQS的內部類ConditionObject。
AQS的ConditionObject請看
AQS的Condition源碼解析

3.6 getOwner

這個方法獲取線程節點的線程的方法。
在這裏插入圖片描述
這個方法返回線程節點存儲的線程。
線程節點的狀態等於0表示鎖空閒,那麼就表示線程節點對應的線程已經被清空了(頭結點)。

3.7 getHoldCount

在這裏插入圖片描述
如果是獨佔模式,那麼返回當前線程持有鎖的層數。

3.8 isLocked

查詢此鎖是否由任意線程保持。此方法用於監視系統狀態,不用於同步控制。
在這裏插入圖片描述

3.9 readObject

在這裏插入圖片描述
鎖在從持久化數據中讀取的時候,所有線程節點都會被設置爲初始化狀態。

4. ReentrantLock 內部NonfairSync和FairSync繼承Sync

4.1 FairSync–lock

在這裏插入圖片描述
FairSync的lock方法,使用的是AQS的acquire方法。
AQS的acquire方法請看
Java基礎–AQS原理
中的第5.6.2小節
在這裏插入圖片描述
lock的公平性是由FairSync的tryAcquire方法實現的。

4.2 FairSync–tryAcquire

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

// 公平嘗試獲取鎖,這裏傳入的值是1
protected final boolean tryAcquire(int acquires) {
	// 獲取當前線程
    final Thread current = Thread.currentThread();
    // 獲取線程節點的等待狀態
    int c = getState();
    // 如果線程節點的等待狀態爲0表示鎖空閒
    if (c == 0) {
    	// 當前線程前面是否還有等待的線程,沒有返回false
        if (!hasQueuedPredecessors() &&
        	// 如果當前線程前面沒有等待的線程,那麼就當前線程的節點的等待狀態設置爲取消,表示當前線程節點
        	// 已經獲取到了鎖,不需要再參與調度了
            compareAndSetState(0, acquires)) {
            // 設置鎖持有線程是當前線程
            setExclusiveOwnerThread(current);
            // 返回公平獲取鎖成功
            return true;
        }
    }
    // 如果線程節點的等待狀態不是0,表示鎖不空閒,鎖已經被線程持有了
    // 如果持有鎖的線程和當前線程相等,那麼表示鎖重入
    else if (current == getExclusiveOwnerThread()) {
    	// 將鎖重入層數加1
        int nextc = c + acquires;
        // 鎖重入層數應該大於等大於0
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 每重入一次,鎖的重入層數加1
        setState(nextc);
        // 返回公平獲取鎖成功
        return true;
    }
    // 公平獲取鎖失敗
    return false;
}
// 這個方法是AQS的方法;在等待競爭隊列中當前線程之前是否還有其他線程
// 只需要看等待競爭隊列中第一個有效的節點的線程和當前線程是不是相等即可,因爲第一個有效的和當前不等,或者隊列空,那麼就是沒有
// 沒有返回false,否則返回true
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    // 獲取等待競爭隊列的隊列尾
    Node t = tail; // Read fields in reverse initialization order
    // 獲取等待競爭隊裏的隊列頭
    Node h = head;
    // 定義頭結點的後繼節點
    Node s;
    return h != t && // 如果頭結點和尾節點重合,那麼表示等待競爭隊列是一個空隊列(裏面有頭結點,但是頭結點不存儲線程,不是線程節點)
        ((s = h.next) == null || s.thread != Thread.currentThread());
        // 只有頭結點,也是相當於等待競爭隊列中沒有其他線程了
        // 或者是當前線程與第一個有效的線程節點的線程不相等
}

4.3 NonfairSync–lock

在這裏插入圖片描述
如果當前線程的線程節點已經獲取到鎖了,或者說鎖空閒,那麼就設置當前線程節點的等待狀態是取消(即不需要參與競爭)。
然後設置鎖持有的線程是當前線程。
如果當前線程的線程節點還沒有獲取鎖,那麼調用AQS的acquire自旋獲取鎖。
獲取鎖是否公平由tryAcquire方法決定。

4.4 NonfairSync–tryAcquire

這是不公平嘗試獲取鎖的時序圖
不公平嘗試獲取鎖的方法直接代理使用ReentrantLock內部Sync的nonfairTryAcquire方法。
詳細請見3.2.
在這裏插入圖片描述
在這裏插入圖片描述

5. ReentrantLock 的構造

我們知道ReentrantLock內部類Sync,且Sync有兩個實現NonfairSync和FairSync。那麼我們怎麼決定使用FairSync還是NonfairSync呢?
答案就在ReentrantLock的構造方法中。

5.1 ReentrantLock

在這裏插入圖片描述
使用無參數的構造方法,調用的是不公平重入鎖。

5.2 ReentrantLock(boolean)

在這裏插入圖片描述
使用有參數的構造方法,則根據傳入的boolean值,決定使用哪種重入鎖。
true:公平重入鎖
false:不公平重入鎖

6. 總結

通篇閱讀了ReentrantLock的源碼後,不僅僅瞭解了ReentrantLock的實現結構,以及ReentrantLock的核心原理。
更加重要的是,現在,你可以自由的使用ReentrantLock,不用再模仿使用。
不同的場景,根據ReentrantLock的原理,很容易找到最合適的方法調用。

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