ReentrantLock Condition源碼分析

在看本文前,如果對ReentrantLock不瞭解的,請先看 ReentrantLock源碼分析,以免有些地方無法理解。
代碼的執行說明,都已註釋的方式寫在了代碼中,如果想看某個方法的分析,直接搜索方法名即可。當然,本文中對於在ReentrantLock中詳細說明過的方法以及相關分析(如將節點加入到同步隊列的方法 enq,將節點掛起的方法acquireQueued,waitstatus狀態說明等),在這裏沒有詳細說明,請直接看ReentrantLock源碼分析

Condition.await

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //將當前線程封裝爲Node節點加入到wait隊列中
    Node node = addConditionWaiter();
    //在wait將項城掛起前釋放鎖
    //在釋放鎖的同時,會那單線程的重入次數State。後面再講線程重新掛起到lock上的時候,會還原原有的state
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //判斷當前節點是否是已經在同步隊列上了,如果已經在同步隊列上了,就不需要掛起。
    //當然,這裏將線程掛起後,當線程被single喚醒,將通過這個判斷跳出循環,因爲在single將線程喚醒後會同時將線程Node加入到同步隊列中。
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //如果Node節點是中斷喚醒的,則通過這裏跳出循環
        //這裏需要注意的一個點是給interruptMode賦值了,interruptMode 記錄了不同的中斷方式(拋異常還是interupt)
        //通過後面的分析可以看到,如果Node是因爲中斷導致將Node加入到Lock,則將中斷方式設置爲拋異常。
        //也就是線程還在wait隊列中時候被中斷了,則對外的終端是拋異常告知。如果是在lock中被中斷,則告知的方式是通過interupt方式。
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //執行到這裏,說明被wait的線程已經喚醒了,並且已經將線程加入到了同步隊列中。
    //這裏首先通過acquireQueued將加入到同步隊列中的Node節點掛起
    //一定要注意:一旦線程在這裏通過acquireQueued掛起,線程將不會再繼續往下執行,只有當線獲取鎖之後,纔會從這裏繼續往下執行。
    //並且這裏掛起的時候,會還原線程原本的重入次數savedState 。
    //如果一旦獲取了鎖,會再次判斷是否是拋異常,如果不是,設置中斷方式就是interupt的方式
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    //這裏判斷Node節點的nextWaiter 是否等於空。
    //通過這個判斷當前節點是否還繼續鏈接在wait隊列上,如果還繼續鏈接在wait隊列上,就將其從wait隊列上刪除。
    //通過後面對addConditionWaiter的分析,你會看到,wait是單向隊列,節點之間是通過addConditionWaiter連接的。
    //通過後面分析,你會發現,如果是中斷將節點加入到了同步隊列,並不會將wait隊列上的節點刪掉。
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    //這裏根據不同中斷方式,對外進行不同的中斷相應
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
//這個方法的作用,就是將當前線程封裝成爲Node節點並加入到wait隊列。
//wait是一個單向隊列,firstWaiter 指向頭結點,lastWaiter指向尾巴節點,節點之間通過nextWaiter鏈接,並且處於wait隊列上的正常的節點的waitStatus 狀態都爲CONDITION(如果被喚醒並加入到同步隊列,就會將其改爲0)
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //這裏判斷尾節點的waitStatus是否是CONDITION,如果不是,則通過unlinkCancelledWaiters刪除那些不符合條件的節點。
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //從wait隊列上刪除不符合條件的節點後,就將當前線程節點鏈接到wait隊列上。
    //這裏不用考慮安全問題,因爲既然要調用wait必須要先獲得鎖。
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
//將隊列上,waitStatus 不等於CONDITION的節點去掉。
//這個邏輯很簡單,就是簡單的鏈表操作,就不逐句分析了.
//遍歷鏈表,維護了三個連續指針,中間指針一旦waitStatus 不等於CONDITION,就將其從節點上去掉。
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}
//釋放鎖,這裏之所以使用fullyRelease,意思就是如果一個鎖多次重入,那麼就需要一次性將多次重入一同釋放。
//這對於ReentrantLock來說當前是很簡單的,只需要將state設置成0即可。
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
    	//獲取當前線程持有的state。state也代表了鎖的重入次數
        int savedState = getState();
        //將原state作爲參數,一次性全部釋放掉。釋放成功了,返回wait前持有的鎖的個數
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
//判斷當前節點是否已經存在於同步隊列上了
final boolean isOnSyncQueue(Node node) {
	//如果已經放入了同步隊列。waitStatus一定不爲CONDITION,並且他的prev一定不爲NULL。
	//所以滿足下面的任何一個條件,說明都沒有放到同步隊列上。所以返回false。
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    //如果不爲null,說明一定在同步隊列上了,返回true。
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
     //前面的兩個if如果滿足條件就立即返回了,如果都不滿足,就只有遍歷同步隊列來逐個檢查了,當然比較浪費時間。
    return findNodeFromTail(node);
}
//遍歷lock逐個比較
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
//判斷是否中斷,如果線程沒有中斷,則返回0。如果中斷了,則通過transferAfterCancelledWait判斷是否是放入lock前發生的中斷。
//如果是,則通過拋異常響應中斷,如果不是,則通過interupt的方式響應中斷
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
//判斷是否是放入lock前發生的中斷
final boolean transferAfterCancelledWait(Node node) {
	//如果當前節點的waitstatus爲Node.CONDITION,則當前節點就一定還在wait隊列上。因爲已經發生了中斷,所以就需要將其放入到同步隊列。
	//放入之前,先通過CAS將waitstatus替換爲0。如果替換失敗了,說明其他線程正在將當前節點放入到lock。
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    	//如果替換成功了,則通過enq將節點放入到同步隊列
        enq(node);
        //說明是在wait中發生的中斷,則返回true
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     */
     //上一步說,如果CAS替換失敗,說明有其他線程正在將Node放入到同步隊列中,但是不已經已經放入成功了,可能只是修改了waitstatus,還沒有最終放入,所以這裏如果判斷還沒有完全放入成功的時候,先通過死循環加yield自旋等待一會。
    while (!isOnSyncQueue(node))
        Thread.yield();
    //知道到這,說明是其他線程已經將節點放入了同步隊列,說明不是在wait隊列中發生的中斷。
    return false;
}
//通過不同的中斷方式,對外進行中斷的響應
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();

Condition.signal

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //如果wait隊列有被wait的節點,則進行喚醒第一個節點
    if (first != null)
        doSignal(first);
}
//喚醒wait隊列上的第一個節點
private void doSignal(Node first) {
    do {
    	//判斷是否隊列上只有一個節點,如果是,就將lastWaiter 設置爲null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        //既然要喚醒第一個節點,喚醒後,直接將第一個節點從隊列上移除。
        first.nextWaiter = null;
    //通過transferForSignal將節點加入到同步隊列中。如果加入失敗了,則重新取出wait隊列的第一個節點並喚醒。
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
//將wait節點放入到同步隊列中
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
     //這裏如果CAS失敗,說明當前節點的狀態已經不是CONDITION,說明已經被其他線程喚醒了。
     //返回false之後,外層是一個循環,會繼續取出wait隊列的第一個節點,繼續執行喚醒操作
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
     //將當前節點加入到同步隊列,注意,這裏enq返回值返回的是假如隊列節點的前一個節點,也就是原隊尾節點。
    Node p = enq(node);
    int ws = p.waitStatus;
    //這裏的判斷非常巧妙,我們知道,我們把Node節點從wait移動到同步隊列,如果我們把節點線程喚醒,是沒有問題的。
    //因爲喚醒後執行到await中的acquireQueued的時候,會被重新掛起,但是這樣比較耗費性能,是沒有必要的。
    //所以這裏進行了判斷,如果移入到同步隊列後,發現原尾節點的狀態大於0,或者將尾節點的狀態改爲SIGNAL的時候失敗了,纔會喚醒。並在acquireQueued中重新整理同步隊列並重新掛起。
    //這裏不掛起是沒有問題的,因爲在acquireQueued掛起前判斷,如果當前節點是第一個節點,會直接獲取鎖。如果中斷喚醒了,或繼續從await掛起的地方繼續執行,會繼續在acquireQueued的地方重新掛起。
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

Condition.signalAll

public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //拿到wait隊列,執行doSignalAll
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
//理解了signal,理解這裏的doSignalAll就很簡單了。
//就是遍歷wait隊列上的節點逐個順序取出放入到同步隊列中。
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章