ReentrantLock可重入、可打斷、Condition原理剖析

大隊長11 發表於 2022-05-13

本文緊接上文的AQS原始碼,如果對於ReentrantLock沒有基礎可以先閱讀我的上一篇文章學習ReentrantLock的原始碼

ReentrantLock鎖重入原理

重入加鎖其實就是將AQS的state進行加一操作

然後釋放鎖資源將AQS的state進行減一操作

當state為0時才會徹底的釋放鎖資源

ReentrantLock可打斷原理

在ReentrantLock中可打斷就是在等待鎖的過程中可以被interrupt打斷(需要呼叫lockInterruptibly),lock方法設定了打斷標記,但是隻有線上程獲得鎖的時候才能知道自己有沒有在阻塞的過程中有沒有被打斷。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted; // 返回打斷標記
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())// 我們這邊會檢查打斷,如果打斷的話返Thread.interrupted()
                interrupted = true;  // 這裡將打斷標記置為true後,繼續進入迴圈。直到獲得到鎖返回標記
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

首先我們需要呼叫加鎖的lockInterruptibly()方法

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

可打斷主要原因在如下程式碼解釋。用異常代替了返回標記,讓執行緒可以直接再park的過程中直接結束

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();  // 再被打斷的時候不會將其標記置為true,而是直接丟擲一個異常,打斷當前的等待。
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

ReentrantLock條件變數原理

Condition是一個介面,實際上是ReentrantLock的ConditionObject類作為其實現類。

首先我們建立一個condition就會呼叫其構造方法。其實就是產生一個新的conditionObject

final ConditionObject newCondition() {
    return new ConditionObject();
}

await()原始碼

接下來,簡單剖析一個原始碼

  • 首先我們得知道,ConditionObject中維護著一個等待的雙向連結串列,其實和阻塞連結串列是很相似的,不同在於不需要前驅進行喚醒。然後在ConditionObject中維護頭和尾的引用就是firstWaiter和lastWaiter成員變數。
public final void await() throws InterruptedException {
    if (Thread.interrupted())   // 如果被打斷,拋異常
        throw new InterruptedException();
    Node node = addConditionWaiter();  // 1.向AQS中新增一個等待連結串列的node
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { // 是否在阻塞連結串列中
        LockSupport.park(this); // 不是的話直接進行阻塞起來
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)// 被打斷了就得把它放阻塞連結串列
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  // 多執行緒的遺留狀態處理
        interruptMode = REINTERRUPT;   
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. 新增等待node
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {  
        unlinkCancelledWaiters(); // 這個和阻塞佇列相似不過是全部遍歷,清除不在等待佇列上的node
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)              // 新增到waitting尾部 
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

signal原始碼

signal喚醒原始碼,簡單介紹就是直接將等待的firstWaiter指向的等待連結串列的第一個進行解除阻塞,然後將其放入阻塞連結串列中。不過多贅述。

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}