Java concurrent包源碼走讀(三)

Java concurrent包源碼走讀(二)我們知道AQS中有個(gè)條件隊(duì)列,但是具體它的作用是干什么、它和同步隊(duì)列有個(gè)關(guān)系,接下來(lái)這篇我們來(lái)了解AQS中的條件隊(duì)列。首先我們先看一下和條件隊(duì)列關(guān)聯(lián)的ReentrantLock類。

ReentrantLock

類圖

從類圖中我們可以看到此類實(shí)現(xiàn)Lock,同時(shí)有個(gè)抽象類Sync,Sync這個(gè)類是繼承AbstractQueuedSynchronizer,同時(shí)它也兩個(gè)子類,F(xiàn)airSync和NonfairSync。由此可見(jiàn)ReentrantLock它可以公平的獲取鎖也可以非公平方式獲取鎖。我們通過(guò)源碼還可以看出ReentrantLock特點(diǎn):

  1. 互斥鎖

  2. 支持公平和非公平獲取鎖,默認(rèn)是非公平

  3. 可重入鎖

  4. 支持條件變量(實(shí)現(xiàn)Lock接口)

對(duì)于ReentrantLock獲取釋放鎖的源碼我們就再分析,感興趣的同學(xué)可以走讀,主要看tryAcquire和tryRelease方法。走讀的時(shí)候可以帶著下面的兩個(gè)問(wèn)題?

  1. ReentrantLock如何實(shí)現(xiàn)可重入?

  2. ReentrantLock的Lock為何要需要try catch,并且lock需要在try的外面?

條件隊(duì)列

我們主通過(guò)await和signal方法分析同步隊(duì)列和條件隊(duì)列的交互。

await()方法
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //將節(jié)點(diǎn)放入等待隊(duì)列
    Node node = addConditionWaiter();
    //釋放節(jié)點(diǎn)占的鎖
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //輪詢判斷節(jié)點(diǎn)是否在AQS隊(duì)列
    while (!isOnSyncQueue(node)) {
        //如果在則阻塞節(jié)點(diǎn)對(duì)應(yīng)的線程
        //它是何時(shí)加入到AQS隊(duì)列中呢?signal()
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //喚醒后繼續(xù)競(jìng)爭(zhēng)鎖,失敗后繼續(xù)阻塞
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

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

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    //喚醒等待隊(duì)列第一個(gè)節(jié)點(diǎn),注意只是喚醒,競(jìng)爭(zhēng)到鎖的看AQS隊(duì)列
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

通過(guò)上面源碼的分析,我們知道AQS自己維護(hù)的隊(duì)列是當(dāng)前等待資源的隊(duì)列,AQS會(huì)在資源被釋放后,依次喚醒隊(duì)列中從前到后的所有節(jié)點(diǎn),使他們對(duì)應(yīng)的線程恢復(fù)執(zhí)行。直到隊(duì)列為空。而條件隊(duì)列維護(hù)一個(gè)等待signal信號(hào)的隊(duì)列,兩個(gè)隊(duì)列的作用是不同,事實(shí)上,每個(gè)線程也僅僅會(huì)同時(shí)存在以上兩個(gè)隊(duì)列中的一個(gè),流程是這樣的:

  1. 線程1調(diào)用reentrantLock.lock時(shí),線程被加入到AQS的等待隊(duì)列中。

  2. 線程1調(diào)用await方法被調(diào)用時(shí),該線程從AQS中移除,對(duì)應(yīng)操作是鎖的釋放。

  3. 接著馬上被加入到Condition的等待隊(duì)列中,意味著該線程需要signal信號(hào)。

  4. 線程2因?yàn)榫€程1釋放鎖的關(guān)系,被喚醒,并判斷可以獲取鎖,于是線程2獲取鎖,并被加入到AQS的等待隊(duì)列中。

  5. 線程2調(diào)用signal方法,這個(gè)時(shí)候Condition的等待隊(duì)列中只有線程1一個(gè)節(jié)點(diǎn),于是它被取出來(lái),并被加入到AQS的等待隊(duì)列中。注意,這個(gè)時(shí)候線程1并沒(méi)有被喚醒

  6. signal方法執(zhí)行完畢,線程2調(diào)用reentrantLock.unLock()方法,釋放鎖。這個(gè)時(shí)候因?yàn)锳QS中只有線程1,于是,AQS釋放鎖后按從頭到尾的順序喚醒線程時(shí),線程1被喚醒,于是線程1回復(fù)執(zhí)行。

  7. 直到釋放所整個(gè)過(guò)程執(zhí)行完畢。

可以看到,整個(gè)協(xié)作過(guò)程是靠結(jié)點(diǎn)在AQS的等待隊(duì)列和條件隊(duì)列中來(lái)回移動(dòng)實(shí)現(xiàn)的,條件隊(duì)列維護(hù)了一個(gè)等待信號(hào)的節(jié)點(diǎn),并在適時(shí)的時(shí)候?qū)⒔Y(jié)點(diǎn)加入到AQS的等待隊(duì)列中來(lái)實(shí)現(xiàn)的喚醒操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容