CountDownLatch 源碼淺析



CountDownLatch 介紹

CountDownLatch是一個同步協(xié)助類已亥,允許一個或多個線程等待,直到其他線程完成操作集挺智。

CountDownLatch使用給定的計數(shù)值(count)初始化航缀。await方法會阻塞直到當(dāng)前的計數(shù)值(count)由于countDown方法的調(diào)用達(dá)到0芳誓,在這之后(即,count為0之后)所有等待的線程都會被釋放兔簇,并且隨后對await方法的調(diào)用都會立即返回发绢。這是一個一次性現(xiàn)象 ———— count不會被重置硬耍。如果你需要一個重置count的版本,那么請考慮使用CyclicBarrier边酒。

CountDownLatch是一個通用的同步工具经柴,它能用于許多用途。一個使用’1’計數(shù)值初始化的CountDownLatch服務(wù)作為一個簡單的開關(guān)門:所有執(zhí)行await的線程等待在門口墩朦,直到某個執(zhí)行countDown方法的線程將門打開坯认。一個使用‘N(count)’初始化的CountDownLatch能被用于使一個線程等待,直到N個線程完成某些動作氓涣,或者某些動作已經(jīng)完成N次牛哺。

CountDownLatch一個很有用的性質(zhì)是,它不要求你在可以繼續(xù)進(jìn)行之前調(diào)用countDown方法等待count到達(dá)0劳吠,它只是簡單的防止任何線程超過await方法直到所有的線程都可以通過引润。
也就是說,你可以在任意時刻調(diào)用await痒玩,如果當(dāng)前的count值非0淳附,那么線程會等待直到count為0時才會繼續(xù)往下執(zhí)行,否則如果count值為0凰荚,await方法會立即返回燃观,你可以不被阻塞的繼續(xù)往下執(zhí)行。

內(nèi)存一致性作用:直到count到達(dá)0便瑟,一個線程調(diào)用countDown()方法之前的動作 happen-before 從另一個線程相應(yīng)的await()方法返回之后的動作缆毁。
比如,threadB.await()到涂、threadA.countDown()脊框,那么threadA執(zhí)行countDown()之前的動作,對于threadB的await()方法之后的動作都可見(當(dāng)count為0時践啄,threadB會從await()方法的阻塞中結(jié)束而繼續(xù)往下執(zhí)行)浇雹。

AbstractQueuedSynchronizer

因?yàn)镃ountDownLatch是使用AbstractQueuedSynchronizer(AQS)的state來實(shí)現(xiàn)其同步控制的。CountDownLatch使用的是共享鎖模式屿讽,由于AQS除了共享鎖模式還有排他鎖模式昭灵,本文僅對CountDownLatch涉及到的共享鎖模式部分的內(nèi)容進(jìn)行介紹,關(guān)于排他鎖模式的部分會在 ReentrantLock 源碼淺析一文中介紹伐谈。

AQS提供一個框架用于實(shí)現(xiàn)依賴于先進(jìn)先出(FIFO)等待隊列的阻塞鎖和同步器(信號量烂完,事件等)。這個類被設(shè)計與作為一個有用的基類诵棵,一個依賴單一原子值為代表狀態(tài)的多種同步器的基類抠蚣。子類必須將修改這個狀態(tài)值的方法定義為受保護(hù)的方法,并且該方法會根據(jù)對象(即履澳,AbstractQueuedSynchronizer子類)被獲取和釋放的方式來定義這個狀態(tài)嘶窄。根據(jù)這些怀跛,這個類的其他方法實(shí)現(xiàn)所有排隊和阻塞的機(jī)制。子類能夠維護(hù)其他的狀態(tài)屬性柄冲,但是只有使用『getState』方法吻谋、『setState』方法以及『compareAndSetState』方法來原子性的修改 int 狀態(tài)值的操作才能遵循相關(guān)同步性。

等待隊列節(jié)點(diǎn)類 ——— Node

等待隊列是一個CLH鎖隊列的變體羊初。CLH通常被用于自旋鎖(CLH鎖是一種基于鏈表的可擴(kuò)展滨溉、高性能、公平的自旋鎖长赞,申請線程只在本地變量上自旋晦攒,它不斷輪詢前驅(qū)的狀態(tài),如果發(fā)現(xiàn)前驅(qū)釋放了鎖就結(jié)束自旋得哆。)脯颜。我們用它來代替阻塞同步器,但是使用相同的基本策略贩据,該策略是持有一些關(guān)于一個線程在它前驅(qū)節(jié)點(diǎn)的控制信息栋操。一個“status”字段在每個節(jié)點(diǎn)中用于保持追蹤是否一個線程需要被阻塞。一個節(jié)點(diǎn)會得到通知當(dāng)它的前驅(qū)節(jié)點(diǎn)被釋放時饱亮。隊列中的每一個節(jié)點(diǎn)都作為一個持有單一等待線程的特定通知風(fēng)格的監(jiān)視器矾芙。狀態(tài)字段不會控制線程是否被授予鎖等。一個線程可能嘗試去獲取鎖如果它在隊列的第一個近上。但是首先這并不保證成功剔宪,它只是給與了競爭的權(quán)力(也就是說,隊列中第一個線程嘗試獲取鎖時壹无,并不保證一定能得到鎖葱绒,它只是有競爭鎖的權(quán)力而已)。所以當(dāng)前被釋放的競爭者線程可能需要重新等待獲取鎖斗锭。
(這里說的"隊列中的第一個的線程"指的時地淀,從隊列頭開始往下的節(jié)點(diǎn)中,第一個node.thread != null的線程岖是。因?yàn)榘锘伲珹QS隊列的head節(jié)點(diǎn)是一個虛節(jié)點(diǎn),不是有個有效的等待節(jié)點(diǎn)豺撑,因此head節(jié)點(diǎn)的thread是為null的作箍。)

為了排隊進(jìn)入一個CLH鎖,你可以原子性的拼接節(jié)點(diǎn)到隊列中作為一個新的隊尾前硫;對于出隊,你只要設(shè)置頭字段荧止。(即屹电,入隊操作時新的節(jié)點(diǎn)會排在CLH鎖隊列的隊尾阶剑,而出隊操作就是將待出隊的node設(shè)置為head。由此可見危号,在AQS中維護(hù)的這個等待隊列牧愁,head是一個無效的節(jié)點(diǎn)。初始化時head是一個new Node()節(jié)點(diǎn)外莲;在后期的操作中猪半,需要出隊的節(jié)點(diǎn)就會設(shè)置到head中。)

          +------+  prev +-----+       +-----+
     head |      | <---- |     | <---- |     |  tail
          +------+       +-----+       +-----+

插入到一個CLH隊列的請求只是一個對“tail”的單個原子操作偷线,所以有一個簡單的從未入隊到入隊的原子分割點(diǎn)磨确。類似的,出隊調(diào)用只需要修改“head”声邦。然而乏奥,節(jié)點(diǎn)需要更多的工作來確定他們的后繼者是誰,部分是為了處理由于超時和中斷而導(dǎo)致的可能的取消亥曹。
(也就是說邓了,一個node的后繼節(jié)點(diǎn)不一定就是node.next,因?yàn)殛犃兄械墓?jié)點(diǎn)可能因?yàn)槌瑫r或中斷而取消了媳瞪,而這些取消的節(jié)點(diǎn)此時還沒被移除隊列(也許正在移除隊列的過程中)骗炉,而一個node的后繼節(jié)點(diǎn)指的是一個未被取消的有效節(jié)點(diǎn),因此在下面的操作中你就會發(fā)現(xiàn)蛇受,在尋找后繼節(jié)點(diǎn)時句葵,尋找的都是當(dāng)前節(jié)點(diǎn)后面第一個有效節(jié)點(diǎn),即非取消節(jié)點(diǎn)龙巨。)

“prev”(前驅(qū))連接(原始的CLH鎖是不使用前驅(qū)連接的)笼呆,主要用于處理取消。如果一個節(jié)點(diǎn)被取消了旨别,它的后驅(qū)(通常)會重連接到一個未被取消的前驅(qū)诗赌。

另外我們使用“next”連接去實(shí)現(xiàn)阻塞機(jī)制。每個節(jié)點(diǎn)的線程ID被它們自己的節(jié)點(diǎn)所持有秸弛,所以前驅(qū)節(jié)點(diǎn)通知下一個節(jié)點(diǎn)可以被喚醒铭若,這是通過遍歷下一個鏈接(即,next字段)來確定需要喚醒的線程递览。后繼節(jié)點(diǎn)的決定必須同‘新入隊的節(jié)點(diǎn)在設(shè)置它的前驅(qū)節(jié)點(diǎn)的“next”屬性操作(即叼屠,新入隊節(jié)點(diǎn)為newNode,在newNode的前驅(qū)節(jié)點(diǎn)preNewNode進(jìn)行preNewNode.next = newNode操作)’產(chǎn)生競爭绞铃。一個解決方法是必要的話當(dāng)一個節(jié)點(diǎn)的后繼看起來是空的時候镜雨,從原子更新“tail”向前檢測儿捧。(或者換句話說荚坞,next鏈接是一個優(yōu)化挑宠,所以我們通常不需要反向掃描。)

取消引入了對基本算法的一些保守性颓影。當(dāng)我們必須為其他節(jié)點(diǎn)的取消輪詢時各淀,我們不需要留意一個取消的節(jié)點(diǎn)是在我們節(jié)點(diǎn)的前面還是后面。它的處理方式是總是根據(jù)取消的節(jié)點(diǎn)喚醒其后繼節(jié)點(diǎn)诡挂,允許它們?nèi)ミB接到一個新的前驅(qū)節(jié)點(diǎn)碎浇,除非我們能夠標(biāo)識一個未被取消的前驅(qū)節(jié)點(diǎn)來完成這個責(zé)任。

  • waitStatus
volatile int waitStatus;

狀態(tài)屬性璃俗,只有如下值:
① SIGNAL:
static final int SIGNAL = -1;
這個節(jié)點(diǎn)的后繼(或者即將被阻塞)被阻塞(通過park阻塞)了奴璃,所以當(dāng)前節(jié)點(diǎn)需要喚醒它的后繼當(dāng)它被釋放或者取消時。為了避免競爭旧找,獲取方法必須首先表示他們需要一個通知信號溺健,然后再原子性的嘗試獲取鎖,如果失敗钮蛛,則阻塞鞭缭。
也就是說,在獲取鎖的操作中魏颓,需要確保當(dāng)前node的preNode的waitStatus狀態(tài)值為’SIGNAL’岭辣,才可以被阻塞,當(dāng)獲取鎖失敗時甸饱。(『shouldParkAfterFailedAcquire』方法的用意就是這)
② CANCELLED:
static final int CANCELLED = 1;
這個節(jié)點(diǎn)由于超時或中斷被取消了沦童。節(jié)點(diǎn)不會離開(改變)這個狀態(tài)。尤其叹话,一個被取消的線程不再會被阻塞了偷遗。
③ CONDITION:
static final int CONDITION = -2;
這個節(jié)點(diǎn)當(dāng)前在一個條件隊列中。它將不會被用于當(dāng)做一個同步隊列的節(jié)點(diǎn)直到它被轉(zhuǎn)移到同步隊列中驼壶,轉(zhuǎn)移的同時狀態(tài)值(waitStatus)將會被設(shè)置為0氏豌。(這里使用這個值將不會做任何事情與該字段其他值對比,只是為了簡化機(jī)制)热凹。
④ PROPAGATE:
static final int PROPAGATE = -3;
一個releaseShared操作必須被廣播給其他節(jié)點(diǎn)泵喘。(只有頭節(jié)點(diǎn)的)該值會在doReleaseShared方法中被設(shè)置去確保持續(xù)的廣播,即便其他操作的介入般妙。
⑤ 0:不是上面的值的情況纪铺。
這個值使用數(shù)值排列以簡化使用。非負(fù)的值表示該節(jié)點(diǎn)不需要信號(通知)碟渺。因此鲜锚,大部分代碼不需要去檢查這個特殊的值,只是為了標(biāo)識。
對于常規(guī)的節(jié)點(diǎn)該字段會被初始化為0芜繁,競爭節(jié)點(diǎn)該值為CONDITION攒霹。這個值使用CAS修改(或者可能的話,無競爭的volatile寫)浆洗。

  • prev
volatile Node prev

連接到前驅(qū)節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)/線程依賴與這個節(jié)點(diǎn)waitStatus的檢測集峦。分配發(fā)生在入隊時伏社,并在出隊時清空(為了GC)。并且塔淤,一個前驅(qū)的取消摘昌,我們將短路當(dāng)發(fā)現(xiàn)一個未被取消的節(jié)點(diǎn)時,未被取消的節(jié)點(diǎn)總是存在因?yàn)轭^節(jié)點(diǎn)不能被取消:只有在獲取鎖操作成功的情況下一個節(jié)點(diǎn)才會成為頭節(jié)點(diǎn)高蜂。一個被取消的線程絕不會獲取成功聪黎,一個線程只能被它自己取消,不能被其他線程取消备恤。

  • next
volatile Node next

連接到后繼的節(jié)點(diǎn)稿饰,該節(jié)點(diǎn)是當(dāng)前的節(jié)點(diǎn)/線程釋放喚醒的節(jié)點(diǎn)。分配發(fā)生在入隊時露泊,在繞過取消的前驅(qū)節(jié)點(diǎn)時進(jìn)行調(diào)整喉镰,并在出隊列時清空(為了GC的緣故)。一個入隊操作(enq)不會被分配到前驅(qū)節(jié)點(diǎn)的next字段惭笑,直到tail成功指向當(dāng)前節(jié)點(diǎn)之后(通過CAS來將tail指向當(dāng)前節(jié)點(diǎn)侣姆。『enq』方法實(shí)現(xiàn)中沉噩,會先將node.prev = oldTailNode;在需要在CAS成功之后捺宗,即tail = node之后,再將oldTailNode.next = node;)川蒙,所以當(dāng)看到next字段為null時并不意味著當(dāng)前節(jié)點(diǎn)是隊列的尾部了蚜厉。無論如何,如果一個next字段顯示為null派歌,我們能夠從隊列尾向前掃描進(jìn)行復(fù)核弯囊。被取消的節(jié)點(diǎn)的next字段會被設(shè)置為它自己,而不是一個null胶果,這使得isOnSyncQueue方法更簡單匾嘱。

  • thread
volatile Thread thread

這個節(jié)點(diǎn)的入隊線程。在構(gòu)建時初始化早抠,在使用完后清除霎烙。

  • nextWaiter
Node nextWaiter

鏈接下一個等待條件的節(jié)點(diǎn),或者一個指定的SHARED值。因?yàn)橹挥谐钟信潘i時能訪問條件隊列悬垃,所以我們只需要一個簡單的單鏈表來維持正在等待條件的節(jié)點(diǎn)游昼。它們接下來會被轉(zhuǎn)換到隊列中以去重新獲取鎖。因?yàn)橹挥信潘i才有conditions尝蠕,所以我們使用給一個特殊值保存的字段來表示共享模式烘豌。
也就是說,nextWaiter用于在排他鎖模式下表示正在等待條件的下一個節(jié)點(diǎn)看彼,因?yàn)橹挥信潘i模式有conditions廊佩;所以在共享鎖模式下,我們使用’SHARED’這個特殊值來表示該字段靖榕。

源碼分析

初始化
CountDownLatch doneSignal = new CountDownLatch(N);

CountDownLatch 使用了共享鎖模式标锄。CountDownLatch 使用了一個內(nèi)部類 Sync來實(shí)現(xiàn)CountDownLatch的同步控制,而Sync是AQS的一個實(shí)現(xiàn)類茁计,它使用AQS的狀態(tài)(state)來表示count料皇。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

構(gòu)造一個CountDownLatch使用給定的count值進(jìn)行初始化。
count值最終是設(shè)置到sync(AbstractQueuedSynchronizer)里的state字段星压。

阻塞的流程分析

『await()』

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

導(dǎo)致當(dāng)前的線程等待直到latch被倒數(shù)到0践剂,或者線程被中斷了。
如果當(dāng)前的count是0租幕,那么方法會立即返回舷手,并且返回值為true。
如果當(dāng)前的count大于0劲绪,則當(dāng)前線程因?yàn)榫€程調(diào)度而變得不可用男窟,并且處于休眠狀態(tài),直到發(fā)生下面二件事之一:
① 由于countDown方法的調(diào)用當(dāng)前的count達(dá)到0贾富;
如果count達(dá)到0歉眷,那么這個方法將返回true。
② 其他線程中斷了當(dāng)前的線程颤枪;
如果當(dāng)前線程在進(jìn)入這個方法時設(shè)置了中斷狀態(tài)汗捡;或者當(dāng)前線程在等待時被設(shè)置了中斷狀態(tài),那么“InterruptedException”異常將會拋出畏纲,并且當(dāng)前的線程的中斷狀態(tài)會被清除扇住。


『acquireSharedInterruptibly』

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

獲取一個共享模式鎖,如果發(fā)生中斷則異常終止盗胀。實(shí)現(xiàn)首先會檢查中斷的狀態(tài)艘蹋,然后執(zhí)行至少一次的tryAcquireShared,成功的話返回票灰。否則女阀,線程將會入隊宅荤,可能會重復(fù)的阻塞和解阻塞,執(zhí)行tryAcquireShared直到成功或者線程被中斷浸策。
① 首先判斷當(dāng)前的線程是否被標(biāo)志為了中斷冯键,如果被標(biāo)志位了中斷,則拋出“InterruptedException”異常庸汗,并清除中斷標(biāo)志惫确;否則到第②步;
② 執(zhí)行『tryAcquireShared』來嘗試獲取鎖蚯舱,如果成功(即雕薪,返回>=0)。則返回true退出方法晓淀;否則到第③步
③ 執(zhí)行doAcquireSharedInterruptibly。


『doAcquireSharedInterruptibly』

① 創(chuàng)建一個共享模式的節(jié)點(diǎn)盏档,并將這個節(jié)點(diǎn)加入到等待隊列中凶掰。
② 獲取新創(chuàng)建好節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)。如果前驅(qū)節(jié)點(diǎn)是head節(jié)點(diǎn)蜈亩,則說明當(dāng)前節(jié)點(diǎn)是隊列中第一個等待獲取鎖的節(jié)點(diǎn)懦窘,那么就執(zhí)行『tryAcquireShared』方法嘗試獲取共享鎖。tryAcquireShared是由CountDownLatch重寫的方法稚配。具體實(shí)現(xiàn)下面會詳細(xì)說明畅涂。這里先給出結(jié)果就是tryAcquireShared方法的返回值會小于0.也就說獲取共享鎖失敗。進(jìn)入步驟③
③ 如果前驅(qū)節(jié)點(diǎn)不是head節(jié)點(diǎn)道川,或者當(dāng)前節(jié)點(diǎn)獲取共享鎖失斘缢ァ(即,步驟②)冒萄。那么執(zhí)行『shouldParkAfterFailedAcquire』方法臊岸,該方法返回true則說明本次獲取共享鎖失敗需要阻塞(掛起)當(dāng)前線程。接著執(zhí)行『parkAndCheckInterrupt』方法尊流,該方法會將當(dāng)前線程掛起帅戒,直到被喚醒。
這就是阻塞情況下的一個主流程崖技,可以知道的是逻住,在這個邏輯過程中使用了大量的CAS來進(jìn)行原子性的修改,當(dāng)修改失敗的時候迎献,是會通過for(;;)來重新循環(huán)的瞎访,也就是說『doAcquireSharedInterruptibly』使用自旋鎖(自旋+CAS)來保證在多線程并發(fā)的情況下,隊列節(jié)點(diǎn)狀態(tài)也是正確的以及在等待隊列的正確性忿晕,最終使得當(dāng)前節(jié)點(diǎn)要么獲取共享鎖成功装诡,要么被掛起等待喚醒银受。


下面我們來對阻塞情況下,涉及的方法進(jìn)行進(jìn)一步的展開鸦采。
『addWaiter』

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

根據(jù)給定的模式創(chuàng)建當(dāng)前線程的節(jié)點(diǎn)宾巍,并將創(chuàng)建好的節(jié)點(diǎn)入隊(加入等待隊列尾部)。
首先在隊列非空的情況下會嘗試一次快速入隊渔伯,也就是通過嘗試一次CAS操作入隊顶霞,如果CAS操作失敗,則調(diào)用enq方法進(jìn)行“自旋+CAS”方法將創(chuàng)建好的節(jié)點(diǎn)加入隊列尾锣吼。
在共享模式下选浑,Node的mode(即,waitStatus)為’SHARED’玄叠。waitStatus是用于在排他鎖模式下當(dāng)節(jié)點(diǎn)處于條件隊列時表示下一個等待條件的節(jié)點(diǎn)古徒,所以在共享鎖模式下,我們使用’SHARED’這個特殊值來表示該字段读恃。


『enq』

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

使用自旋鎖的方式(自旋+CAS)插入節(jié)點(diǎn)到等待隊列隧膘,如果等待隊列為空則初始化隊列。
初始化隊列:創(chuàng)建一個空節(jié)點(diǎn)(即寺惫,new Node())疹吃,將head和tail都指向這個節(jié)點(diǎn)。
然后才是將我們待插入的節(jié)點(diǎn)插入西雀,即:emptyNode -> newNode. head指向emptyNode萨驶,tail指向newNode。


『tryAcquireShared』
在共享模式下嘗試獲取艇肴。這個方法需要查詢是否對象的狀態(tài)允許在共享模式下被獲取腔呜,如果允許則去獲取它。
這個方法總是被線程執(zhí)行獲取共享鎖時被調(diào)用再悼。如果這個方法報告失敗育谬,那么獲取方法可能會使線程排隊等待,如果它(即帮哈,線程)還沒入隊的話膛檀,直到其他的線程發(fā)出釋放的信號。
默認(rèn)實(shí)現(xiàn)拋出一個“UnsupportedOperationException”
返回:
a)< 0 : 一個負(fù)數(shù)的返回表示失斈锸獭咖刃;
b) 0 : 0表示在共享模式下獲取鎖成功,但是后續(xù)的獲取共享鎖將不會成功
c)> 0 : 大于0表示共享模式下獲取鎖成功憾筏,并且后續(xù)的獲取共享鎖可能也會成功嚎杨,在這種情況下后續(xù)等待的線程必須檢查是否有效。

CountDownLatch對該方法進(jìn)行了重寫:

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

如果當(dāng)前的狀態(tài)值為0(即氧腰,count為0)枫浙,則表示獲取成功(返回’1’)刨肃;否則表示獲取失敿粮(返回’-1’)


『shouldParkAfterFailedAcquire』

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

檢查并修改一個節(jié)點(diǎn)的狀態(tài)黑滴,當(dāng)該節(jié)點(diǎn)獲取鎖失敗時。返回true如果線程需要阻塞挪鹏。這是主要的信號通知控制在所有的獲取鎖循環(huán)中紧帕。要求’pred’ == ‘node.prev’
① 如果pred.waitStatus == Node.SIGNAL盔然。則說明node的前驅(qū)節(jié)點(diǎn)已經(jīng)被要求去通知釋放它的后繼節(jié)點(diǎn),所以node可以安全的被掛起(park)是嗜。然后愈案,退出方法,返回true鹅搪。
② 如果pred.waitStatus > 0站绪。則說明node的前驅(qū)節(jié)點(diǎn)被取消了。那么跳過這個前驅(qū)節(jié)點(diǎn)并重新標(biāo)志一個有效的前驅(qū)節(jié)點(diǎn)(即丽柿,waitStatus <= 0 的節(jié)點(diǎn)可作為有效的前驅(qū)節(jié)點(diǎn))崇众,然后,退出方法航厚,返回false。
③ 其他情況下锰蓬,即pred.waitStatus為’0’或’PROPAGATE’幔睬。表示我們需要一個通知信號(即,當(dāng)前的node需要喚醒的通知)芹扭,但是當(dāng)前還不能掛起node麻顶。調(diào)用『compareAndSetWaitStatus(pred, ws, Node.SIGNAL)』方法通過CAS的方式來修改前驅(qū)節(jié)點(diǎn)的waitStatus為“SIGNAL”。退出方法舱卡,返回false辅肾。
我們需要一個通知信號,主要是因?yàn)楫?dāng)前線程要被掛起了(park)轮锥。而如果waitStatus已經(jīng)是’SIGNAL’的話就無需修改矫钓,直接掛起就好,而如果waitStatus是’CANCELLED’的話舍杜,說明prev已經(jīng)被取消了新娜,是個無效節(jié)點(diǎn)了,那么無需修改這個無效節(jié)點(diǎn)的waitStatus既绩,而是需要先找到一個有效的prev概龄。因此,剩下的情況就只有當(dāng)waitStatus為’0’和’PROPAGAET’了(注意饲握,waitStatus為’CONDITION’是節(jié)點(diǎn)不在等待隊列中私杜,所以當(dāng)下情況waitStatus不可能為’CONDITION’)蚕键,這是我們需要將prev的waitStatus使用CAS的方式修改為’SIGNAL’,而且只有修改成功的情況下衰粹,當(dāng)前的線程才能安全被掛起锣光。
還值得注意的時,因此該方法的CAS操作都是沒有自旋的寄猩,所以當(dāng)它操作完CAS后都會返回false嫉晶,在外層的方法中會使用自旋,當(dāng)發(fā)現(xiàn)返回的是false時田篇,會再次調(diào)用該方法替废,以檢查保證有當(dāng)前node有一個有效的prev,并且其waitStatus為’SIGNAL’泊柬,在此情況下當(dāng)前的線程才會被掛起(park)椎镣。

釋放的流程分析

『countDown』

public void countDown() {
    sync.releaseShared(1);
}

減小latch的count,如果count達(dá)到0則釋放所有正在等待的線程兽赁。
如果當(dāng)前的count大于0状答,那么減少count。如果減少后的count值為0刀崖,那么所有正在等待的線程因?yàn)榫€程調(diào)度的原因被重新啟用惊科。
如果當(dāng)前的count值已經(jīng)是0了,那么什么都不會發(fā)生亮钦。


『releaseShared』

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

共享模式下的釋放馆截。如果『tryReleaseShared』返回true的話,會使一個或多個線程重新啟動蜂莉。


『tryReleaseShared』
在共享模式下蜡娶,嘗試去設(shè)置狀態(tài)來反映一個釋放。
這個方法總是在線程執(zhí)行釋放時被調(diào)用映穗。
默認(rèn)實(shí)現(xiàn)拋出一個UnsupportedOperationException異常窖张。
返回:如果當(dāng)前共享模式可能允許一個正在等待的獲取成功(正在等待的獲取可能是共享模式的,也可能是排他模式的)蚁滋,則返回true宿接;否則,返回false辕录。

CountDownLatch對該方法進(jìn)行了重寫:

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

減少count的值澄阳,如果count為0則發(fā)出釋放信號。
這里使用了"自旋+CAS”的方式來原子性的將state的值減少1踏拜,如果在此過程中state已經(jīng)為0了(在并發(fā)情況下碎赢,可能已經(jīng)被其他線程修改為了0),則返回false速梗。否則根據(jù)修改后state的值是否等于0來返回boolean值肮塞。


『doReleaseShared』

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

共享模式下的釋放動作 ———— 用信號通知后繼節(jié)點(diǎn)并且確保廣播襟齿。(注意:在排他鎖模式下,釋放只是相當(dāng)于調(diào)用head的unparkSuccessor方法如果它需要通知喚醒的話枕赵。)
確保一個釋放的廣播猜欺,即使有其他線程正在進(jìn)行獲取/釋放鎖。這個過程通常的方式是嘗試head的unparkSuccessor操作如果需要通知釋放的話拷窜。如果沒這么做开皿,狀態(tài)會被設(shè)置為‘PROPAGATE’以確保在釋放,廣播繼續(xù)篮昧。此外赋荆,當(dāng)我們正在做這個操作的時候如果新的節(jié)點(diǎn)被添加的話,我們需要重新循環(huán)再進(jìn)行一次該操作懊昨。另外窄潭,不同于unparkSuccessor的其他用途,我們需要知道CAS重置狀態(tài)是否失敗酵颁,如果失敗則重新檢查嫉你。

在隊列非空的時候,該方法會釋放head的后繼節(jié)點(diǎn)躏惋,如果該節(jié)點(diǎn)可以被釋放的話幽污。『(h != null && h != tail)』表示隊列非空簿姨,即有等待獲取鎖的節(jié)點(diǎn)距误;『(h == head)』表示,已經(jīng)操作完釋放后繼節(jié)點(diǎn)款熬,或者隊列已經(jīng)空了(即,『(h == null || h == tail)』)攘乒,那么就退出循環(huán)贤牛。否則如果循環(huán)過程中(即,『h != head』)则酝,頭結(jié)點(diǎn)發(fā)生了變化殉簸,則重新循環(huán)。
如果『if (h != null && h != tail)』為true沽讹,那么:
① 如果head的waitStatus為’SIGNAL’般卑,則說明head的后繼節(jié)點(diǎn)可被通知釋放,那么執(zhí)行CAS操作將head.waitStatus修改為’0’爽雄,如果成功蝠检,則執(zhí)行『unparkSuccessor』對head的后繼節(jié)點(diǎn)進(jìn)行釋放操作,如果CAS操作失敗挚瘟,則說明發(fā)送了多線程競爭(即叹谁,此時有其他線程也在修改head的waitStatus狀態(tài)值)饲梭,那么重新循環(huán)檢查。
② 如果head的waitStatus為’0’焰檩,則使用CAS的方式將其修改為’PROPAGATE’憔涉。如果CAS操作失敗,則說明發(fā)生了多線程競爭析苫,那么重新循環(huán)檢查兜叨。
③ 如果上面的兩個操作中有一個成功了,就會走到“if (h == head)”這一步衩侥,并且此時head節(jié)點(diǎn)沒有發(fā)生變化国旷,則退出循環(huán),操作結(jié)束顿乒。否則议街,說明head節(jié)點(diǎn)發(fā)生變化了,那么重新循環(huán)檢查璧榄。
『if (h != null && h != tail)』為false特漩,那么:
說明隊列中沒有等到獲取鎖的節(jié)點(diǎn)。會直接到“if (h == head)”骨杂,如果此時head節(jié)點(diǎn)沒有發(fā)生變化涂身,則直接退出循環(huán),操作結(jié)束搓蚪。如果此時head節(jié)點(diǎn)發(fā)生了變化蛤售,那么重新循環(huán)檢查。
也就是說妒潭,該方法在等待隊列非空時(即悴能,存在一個有效的等待節(jié)點(diǎn),頭結(jié)點(diǎn)不是有效節(jié)點(diǎn))雳灾,會根據(jù)head的waitStatus進(jìn)行后續(xù)的操作漠酿。
a) 如果『ws == Node.SIGNAL』,則說明需要釋放head后繼節(jié)點(diǎn)谎亩,如果此時CAS操作『compareAndSetWaitStatus(h, Node.SIGNAL, 0)』也成功的話(說明炒嘲,此時沒有其他線程在修改head的waitStatus),那么就會執(zhí)行『unparkSuccessor(h);』來釋放head的后繼節(jié)點(diǎn)匈庭。
b) 如果『ws != Node.SIGNAL』并且『ws == 0』夫凸,則通過CAS操作將head的waitStatus修改為’PROPAGATE’。
以上兩步阱持,當(dāng)CAS失敗夭拌,也就是有其他線程也在修改head的waitStatus狀態(tài)時,需要繼續(xù)循環(huán)進(jìn)行重新檢測,如果head節(jié)點(diǎn)改變了也需要繼續(xù)循環(huán)重新檢測啼止。

Q:關(guān)于node的waitStatus為’0’的情況道逗?
A:當(dāng)節(jié)點(diǎn)不屬于任何waitStatus的話,就會是0献烦。比如滓窍,創(chuàng)建好的節(jié)點(diǎn)。比如巩那,原來是SIGNAL狀態(tài)吏夯,在執(zhí)行完unparkSuccessor操作后(邏輯上說是執(zhí)行完unparkSuccessor后,但實(shí)際的代碼實(shí)現(xiàn)必須先將node的waitStatus通過CAS成功從SINGAL修改為0后即横,才可執(zhí)行unparkSuccessor操作噪生,以保證多線程競爭情況下的正確性)。比如东囚,將節(jié)點(diǎn)從條件隊列轉(zhuǎn)移到等待隊列的時候跺嗽,會通過CAS將node的waitStatus從’CONDITION’修改為0。

Q:’PROPAGATE’狀態(tài)與釋放之間的關(guān)系页藻?
A:當(dāng)head的waitStatus為’PROPAGATE’的話桨嫁,在釋放操作時,這個釋放會被廣播下去份帐,也就是說璃吧,第一個線程被釋放完后,會繼續(xù)釋放第二個被阻塞的線程废境。畜挨。。


『unparkSuccessor』

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

喚醒后繼節(jié)點(diǎn)噩凹,如果存在的話
① 如果狀態(tài)值是負(fù)數(shù)巴元,則在預(yù)期發(fā)信號通知時清除這個負(fù)數(shù)狀態(tài)值。如果狀態(tài)被等待的線程修改了或者清除負(fù)數(shù)狀態(tài)值失敗是允許驮宴。
② 后繼節(jié)點(diǎn)的線程被喚醒逮刨,后繼節(jié)點(diǎn)通常就是下一個節(jié)點(diǎn)。但是如果下一個節(jié)點(diǎn)被取消了或者下一個節(jié)點(diǎn)為null幻赚,則從隊列尾(tail)往前遍歷去找真實(shí)的未取消的后繼節(jié)點(diǎn)禀忆。
『(s == null || s.waitStatus > 0)』:說明下一個節(jié)點(diǎn)為null或被取消了(waitStatus允許的狀態(tài)值中臊旭,只有’CANCELLED’是>0的)落恼。那么,就從隊列尾(tail)開始向前遍歷离熏,獲取第一個非空且未被取消的節(jié)點(diǎn)佳谦。如果存在這樣的一個后繼節(jié)點(diǎn)的話(即,“s != null”)滋戳,則執(zhí)行『LockSupport.unpark(s.thread);』操作來喚醒這個節(jié)點(diǎn)的線程钻蔑。
Q:關(guān)于node的waitStatus為’CANCELLED’的情況啥刻?
A:關(guān)于node的waitStatus為’CANCELLED’的情況:比如,當(dāng)這個node被中斷了咪笑,或者設(shè)置的超時時間到了可帽,那么說明這個線程獲取鎖失敗,那么此時就應(yīng)該將其設(shè)置為cancelled窗怒,因?yàn)槿绻摼€程還需要獲取鎖的話映跟,會重新調(diào)用獲取鎖的方法,而獲取鎖的方法就是創(chuàng)建一個新的node的扬虚。所以努隙,那么線程獲取鎖失敗的時候就會將這個node的waitStatus設(shè)置為’CANCELLED’,一個被取消的線程絕不會獲取鎖成功辜昵,一個線程只能被它自己取消荸镊,不能被其他線程取消。

Q:關(guān)于node為null的情況堪置?
A:關(guān)于node為null的情況:比如躬存,一個入隊操作(enq)不會被分配到前驅(qū)節(jié)點(diǎn)的next字段,直到tail成功指向當(dāng)前節(jié)點(diǎn)之后(通過CAS來將tail指向當(dāng)前節(jié)點(diǎn)晋柱∮殴梗『enq』方法實(shí)現(xiàn)中,會先將node.prev = oldTailNode;在需要在CAS成功之后雁竞,即tail = node之后钦椭,再將oldTailNode.next = node;),所以當(dāng)看到next字段為null時并不意味著當(dāng)前節(jié)點(diǎn)是隊列的尾部了碑诉。無論如何彪腔,如果一個next字段顯示為null,我們能夠從隊列尾向前掃描進(jìn)行復(fù)核进栽。


當(dāng)調(diào)用了『LockSupport.unpark(s.thread);』操作后德挣,等待隊列中第一個等待的線程就會重新啟動。流程回到『doAcquireSharedInterruptibly』方法中快毛,線程從阻塞中恢復(fù):

第一個被釋放的線程從『parkAndCheckInterrupt』方法中的『LockSupport.park(this)』掛起結(jié)束格嗅,繼續(xù)后面的流程。因?yàn)榇藭r是正常的被喚醒流程唠帝,線程并沒有被設(shè)置中斷標(biāo)志屯掖,因此『parkAndCheckInterrupt』會返回false。流程重新開始循環(huán)襟衰。并且通過『Node p = node.predecessor()』為head贴铜,接著執(zhí)行『tryAcquireShared』方法,此時的count==0,所以該方法也會返回’1’绍坝,表示獲取共享鎖成功徘意。接著通過『setHeadAndPropagate』將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)并進(jìn)行廣播如果需要的話。然后將p(即轩褐,舊的head節(jié)點(diǎn))的next置null椎咧,有助于p被垃圾收集器收集。然后標(biāo)識failed為false把介。結(jié)束方法調(diào)用邑退,返回true。


『setHeadAndPropagate』

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

設(shè)置’node’節(jié)點(diǎn)為頭結(jié)點(diǎn)劳澄,并且檢查’node’節(jié)點(diǎn)的后繼是否正在等待獲取共享鎖,如果是的話秒拔,并且'propagate > 0'或者’node’的waitStatus被設(shè)置成了’PROPAGATE’莫矗,則廣播。
① 設(shè)置’node’為head節(jié)點(diǎn)
② 嘗試通知隊列中的下一個節(jié)點(diǎn)砂缩,如果:
??[1]
???a) 調(diào)用者標(biāo)識了廣播(即作谚,propagate > 0),
???b) 或者waitStatus被前面的操作重新記錄了(’h.waitStatus’可能在setHead之前或之后被重新記錄)(注意庵芭,這里使用waitStatus的符號檢查妹懒,因?yàn)镻ROPAGATE狀態(tài)可能被轉(zhuǎn)換為SIGNAL)。
??并且[2]隊列中下一個等待的節(jié)點(diǎn)是共享模式的双吆,或者下一個節(jié)點(diǎn)為null眨唬。
這兩次檢查的保守性可能導(dǎo)致不必要的喚醒,但是只有當(dāng)多線程競爭獲取/釋放鎖時好乐,所以大多數(shù)情況下現(xiàn)在或即將需要通知(signal)喚醒匾竿。(因?yàn)樵趀nq新節(jié)點(diǎn)入隊過程中,可能出現(xiàn)next為null的短暫現(xiàn)象蔚万,這是發(fā)現(xiàn)在節(jié)點(diǎn)入隊的過程中岭妖,隨后節(jié)點(diǎn)就會入隊成功,next字段就不會為null了反璃。所以這里將next為null的情況也考慮了昵慌,在廣播釋放時,會將這個正在入隊的節(jié)點(diǎn)對應(yīng)的線程也進(jìn)行釋放)淮蜈。
如果符合??[1]斋攀、[2]個條件則執(zhí)行『doReleaseShared()』來釋放后繼的節(jié)點(diǎn)。

可設(shè)置超時時間的await

『await(long timeout, TimeUnit unit)』同『await()』方法大體是相同的礁芦,主要多了在獲取共享鎖時對時間的控制蜻韭。
在嘗試獲取鎖時的區(qū)別:
① 如果傳入的給定的超時納秒數(shù)是否小于等于0,如果是則直接返回false柿扣,獲取共享鎖失敗肖方。
② 如果在使用自旋的方式獲取共享鎖的過程中,發(fā)現(xiàn)已經(jīng)過了設(shè)置的超時時間未状,那么直接返回false俯画,獲取共享鎖失敗。
③ 如果當(dāng)前線程無法獲取當(dāng)共享鎖司草,并且『shouldParkAfterFailedAcquire』方法返回true(則說明本次獲取共享鎖失敗需要阻塞/掛起當(dāng)前線程)艰垂。但當(dāng)『nanosTimeout <= spinForTimeoutThreshold』說明設(shè)置的超時時間 <= 自旋超時的閾值。這里spinForTimeoutThreshold的值為1000納秒埋虹,表示當(dāng)設(shè)置的超時時間小于1000納秒時猜憎,使用自旋比使用線程掛起更快。粗略估算這足以去提升響應(yīng)在一個很短的超時時間內(nèi)搔课。否則也是使用『LockSupport.parkNanos(this, nanosTimeout);』將當(dāng)前線程掛起胰柑,直到被喚醒或者超時時間到。

取消節(jié)點(diǎn)

當(dāng)嘗試獲取鎖的節(jié)點(diǎn)爬泥,因?yàn)槌瑫r或中斷而結(jié)束時柬讨,說明本次獲取鎖操作失敗,因?yàn)楸敬尾僮鞯膎ode就應(yīng)該被取消袍啡。如果線程還需要獲取鎖的話踩官,會再次嘗試獲取鎖操作,此時如果需要的話是會生成一個新的node的境输。
『cancelAcquire』

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

① 如果待取消節(jié)點(diǎn)(node)為null蔗牡,則直接返回。
② 將node的thread置為null嗅剖;
③ 將node的prev屬性指向一個在它之前的有效的節(jié)點(diǎn)(即蛋逾,waitStatus <= 0的節(jié)點(diǎn)都為有效節(jié)點(diǎn))。 也就是跳過被取消的前驅(qū)節(jié)點(diǎn)窗悯。
④ 『Node predNext = pred.next;』取pred的下一個節(jié)點(diǎn)区匣。這個predNext是pred表面上的下一個連接的節(jié)點(diǎn)(即,無需考慮該節(jié)點(diǎn)是否被取消了)蒋院。下面的CAS操作將會失斂鞴场(『compareAndSetNext(pred, predNext, null);』or『compareAndSetNext(pred, predNext, next);』),如果和其他的取消或通知操作發(fā)生競爭時欺旧,這時不需要進(jìn)一步的操作姑丑。因?yàn)槿绻a(chǎn)生競爭,說明pred的next已經(jīng)被修改了辞友,并且是最新的值了栅哀,而我們的操作也就沒有要執(zhí)行的必要了震肮。
⑤ 將node的waitStatus設(shè)置為’CANCELLED’。這里可以使用無條件的寫代替CAS(注意留拾,node的waitStatus是volatile的)戳晌。在這個原子操作之后,其他節(jié)點(diǎn)會跳過我們(即痴柔,跳過waitStatus被置位CANCELLED的節(jié)點(diǎn))沦偎,在這個原子操作之前,我們不受其他線程的干擾咳蔚。也就是說豪嚎,無論其他線程對node的waitStatus是否有在操作,在當(dāng)前的情況下我們都需要將這個node的waitStatus置為’CANCELLED’谈火。
⑥ 如果待取消的node節(jié)點(diǎn)是隊列尾節(jié)點(diǎn)的話(即侈询,『node == tail』),那么刪除node自己即可糯耍。使用CAS將tail節(jié)點(diǎn)設(shè)置成前面得到的第一個有效前驅(qū)節(jié)點(diǎn)(即妄荔,『compareAndSetTail(node, pred)』)。并且CAS操作成功的話谍肤,執(zhí)行『compareAndSetNext(pred, predNext, null);』也就是將tail的next置為null的意思啦租。如果該CAS操作失敗的話,沒關(guān)系荒揣。說明此時tail已經(jīng)被修改了篷角。
⑦ 如果待取消的node節(jié)點(diǎn)不是隊尾節(jié)點(diǎn)。并且:
a)pred(即系任,node的有效前驅(qū)節(jié)點(diǎn))不是head節(jié)點(diǎn)恳蹲;并且
b)“pred.waitStatus為SIGNAL” 或者 “pred.waitStatus <= 0”時通過CAS將pred.waitStatus設(shè)置為SIGNAL”成功;并且
c) pred的thread非空
那么俩滥,當(dāng)node的next節(jié)點(diǎn)非空嘉蕾,且next節(jié)點(diǎn)的waitStatus<=0(說明next節(jié)點(diǎn)未被取消)時,通過CAS將pred的next執(zhí)行node的next(即霜旧,pred.next = node.next)错忱。同時,如果該CAS操作失敗是沒關(guān)系的挂据,說明有其他線程操作已經(jīng)修改了該pre的next值以清。
⑧ 如果待取消的node節(jié)點(diǎn)不是隊尾節(jié)點(diǎn),并且步驟[7]條件不成立崎逃。那么執(zhí)行『unparkSuccessor(node);』來釋放當(dāng)前這個待取消節(jié)點(diǎn)的下一個節(jié)點(diǎn)掷倔。(也就是說,當(dāng)prev是head節(jié)點(diǎn)个绍,或者prev也被取消的話勒葱,會執(zhí)行『unparkSuccessor(node);』來釋放node的下一個節(jié)點(diǎn)浪汪,其實(shí)也就是pred的下一個節(jié)點(diǎn))


從上面的分析我們可以知道,其實(shí)CountDownLatch中線程的釋放其實(shí)是有順序的凛虽,根據(jù)節(jié)點(diǎn)入隊的順序依次被釋放死遭,先入隊的節(jié)點(diǎn)的線程會先被釋放。

后記

如果文章有錯不吝指教 :)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涩维,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子袁波,更是在濱河造成了極大的恐慌瓦阐,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篷牌,死亡現(xiàn)場離奇詭異睡蟋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枷颊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門戳杀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夭苗,你說我怎么就攤上這事信卡。” “怎么了题造?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵傍菇,是天一觀的道長。 經(jīng)常有香客問我界赔,道長丢习,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任淮悼,我火速辦了婚禮咐低,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袜腥。我一直安慰自己见擦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布羹令。 她就那樣靜靜地躺著锡宋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪特恬。 梳的紋絲不亂的頭發(fā)上执俩,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機(jī)與錄音癌刽,去河邊找鬼役首。 笑死尝丐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衡奥。 我是一名探鬼主播爹袁,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼矮固!你這毒婦竟也來了失息?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤档址,失蹤者是張志新(化名)和其女友劉穎盹兢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體守伸,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绎秒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尼摹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片见芹。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蠢涝,靈堂內(nèi)的尸體忽然破棺而出玄呛,到底是詐尸還是另有隱情,我是刑警寧澤和二,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布把鉴,位于F島的核電站,受9級特大地震影響儿咱,放射性物質(zhì)發(fā)生泄漏庭砍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一混埠、第九天 我趴在偏房一處隱蔽的房頂上張望怠缸。 院中可真熱鬧,春花似錦钳宪、人聲如沸揭北。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搔体。三九已至,卻和暖如春半醉,著一層夾襖步出監(jiān)牢的瞬間疚俱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工缩多, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呆奕,地道東北人养晋。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像梁钾,于是被迫代替她去往敵國和親绳泉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容