主線程等待子線程執(zhí)行結(jié)束再執(zhí)行—— CountDownLatch

一酗昼、一個例子

private static void countDownLatchTest(List<String> roles){
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i=0;i<10;i++){
CountRunnable countRunnable = new CountRunnable(i+"",i+"",roles,countDownLatch);
new Thread(countRunnable).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public class CountRunnable implements Runnable{

private String id;
private String name;
private List<String> roles;
private CountDownLatch countDownLatch;
private String password;

public CountRunnable(String id, String name, List<String> roles,
                     CountDownLatch countDownLatch){
    this.name = name;
    this.id = id;
    this.roles = roles;
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    roles.add(name);
    System.out.println("執(zhí)行了-id:"+id+"name:"+name+"roles:"+roles.size());
    try {
        countDownLatch.countDown();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public List<String> getRoles() {
    return roles;
}

public void setRoles(List<String> roles) {
    this.roles = roles;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

}
輸出結(jié)果:

開始:1515333020630
執(zhí)行了-id:0name:0roles:1
執(zhí)行了-id:1name:1roles:2
執(zhí)行了-id:4name:4roles:4
執(zhí)行了-id:8name:8roles:7
執(zhí)行了-id:3name:3roles:9
執(zhí)行了-id:7name:7roles:10
執(zhí)行了-id:9name:9roles:8
執(zhí)行了-id:6name:6roles:6
執(zhí)行了-id:5name:5roles:5
執(zhí)行了-id:2name:2roles:4
[0, 1, 2, 4, 5, 6, 8, 9, 3, 7]
結(jié)束:1515333021639耗時:1009roles size:10
可以看出石挂,上面每個線程執(zhí)行1000ms,總時長1009ms.

二颠通、源碼介紹

/**

  • Constructs a {@code CountDownLatch} initialized with the given count.
  • @param count the number of times {@link #countDown} must be invoked
  •    before threads can pass through {@link #await}
    
  • @throws IllegalArgumentException if {@code count} is negative
    */
    public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
    }
    這里看清楚一點:構(gòu)造器中要指定大于0的參數(shù)。

然后創(chuàng)建了一個sync對象致讥,這個Sync是一個內(nèi)部類:

/**

  • Synchronization control For CountDownLatch.

  • Uses AQS state to represent count.
    */
    private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
    setState(count);
    }

    int getCount() {
    return getState();
    }

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

    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;
    }
    }
    }
    可以看到仅仆,Sync繼承自AQS類的內(nèi)部類,是CountDownLatch的核心實現(xiàn)垢袱。在內(nèi)部類里有幾個方法:

getCount:獲取當(dāng)前等待的線程數(shù)
tryAcquireShared:如果線程數(shù)為0才返回1墓拜,即當(dāng)前沒有等待的線程
tryReleaseShared:重寫AQS方法,實現(xiàn)每被countDown調(diào)用请契,就將標(biāo)志位-1咳榜,直到標(biāo)志位為0。
而CountDownLatch的里的幾個方法:

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

public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

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

public long getCount() {
return sync.getCount();
}
acquireSharedInterruptibly:

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判斷是否發(fā)生中斷
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示獲取到了共享鎖爽锥,1表示沒有獲取共享鎖
doAcquireSharedInterruptibly(arg);
}
繼續(xù)深入doAcquireSharedInterruptibly涌韩,這段我是看不明白了:

private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//由于采用的公平鎖,所以要將節(jié)點放到隊列里
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {//本質(zhì)是等待共享鎖的釋放
final Node p = node.predecessor();//獲得節(jié)點的前繼
if (p == head) { //如果前一個節(jié)點等于前繼
int r = tryAcquireShared(arg);//就判斷嘗試獲取鎖
/*
這里要注意一下r的值就2種情況-1和1:
情況1.r為-1,latch沒有調(diào)用countDown(),state是沒有變化的導(dǎo)致state一直大于0或者調(diào)用了countDown(),但是state不等于0氯夷,直接在for循環(huán)中等待
情況2.r為1,證明countDown(),已經(jīng)減到0,當(dāng)前線程還在隊列中臣樱,state已經(jīng)等于0了.接下來就是喚醒隊列中的節(jié)點
*/
if (r >= 0) {
setHeadAndPropagate(node, r);//將當(dāng)前節(jié)點設(shè)置頭結(jié)點。
p.next = null; // help GC 刪除舊的頭結(jié)點
failed = false;
return;
}
}
//當(dāng)前節(jié)點不是頭結(jié)點肠槽,當(dāng)前線程一直等待擎淤,直到獲取到共享鎖奢啥。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate:

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 記錄頭結(jié)點
setHead(node);//設(shè)置node為頭結(jié)點
/*
*從上面?zhèn)鬟f過來 propagate = 1秸仙;
*一定會進入下面的判斷
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//獲得當(dāng)前節(jié)點的下一個節(jié)點,如果為最后一個節(jié)點或者桩盲,為shared
if (s == null || s.isShared())
doReleaseShared();//釋放共享鎖
}
}
isShared:

final boolean isShared() {
return nextWaiter == SHARED;
}
釋放共享鎖寂纪,通知后面的節(jié)點:

private void doReleaseShared() {
for (;;) {
Node h = head;//獲得頭結(jié)點
if (h != null && h != tail) {
int ws = h.waitStatus;//獲取頭結(jié)點的狀態(tài)默認值為0
if (ws == Node.SIGNAL) {如果等于SIGNAL喚醒狀態(tài)
//將頭結(jié)點的狀態(tài)置成0,并使用Node.SIGNAL(-1)與0比較,continue赌结,h的狀態(tài)設(shè)置為0,不會再進入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判斷ws是否為0捞蛋,并且h的狀態(tài)不等于0,這里是個坑啊柬姚,ws等于0拟杉,h就是0啊,所以if進不來的,并設(shè)置節(jié)點為PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
如果頭結(jié)點等于h,其實也沒有一直loop量承,由于上面寫的Node h = head,就算前面的條件都不滿足搬设,這里一定會break
if (h == head) // loop if head changed
break;
}
}
深入到這里穴店,以超出我的想象力。

三拿穴、CountDownLatch總結(jié)

CountDownLatch是通過“共享鎖”實現(xiàn)的泣洞。在創(chuàng)建CountDownLatch中時,會傳遞一個int類型參數(shù)count默色,該參數(shù)是“鎖計數(shù)器”的初始狀態(tài)球凰,表示該“共享鎖”最多能被count個線程同時獲取。當(dāng)某線程調(diào)用該CountDownLatch對象的await()方法時腿宰,該線程會等待“共享鎖”可用時呕诉,才能獲取“共享鎖”進而繼續(xù)運行。而“共享鎖”可用的條件酗失,就是“鎖計數(shù)器”的值為0义钉!而“鎖計數(shù)器”的初始值為count,每當(dāng)一個線程調(diào)用該CountDownLatch對象的countDown()方法時规肴,才將“鎖計數(shù)器”-1捶闸;通過這種方式,必須有count個線程調(diào)用countDown()之后拖刃,“鎖計數(shù)器”才為0删壮,而前面提到的等待線程才能繼續(xù)運行。


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兑牡,一起剝皮案震驚了整個濱河市央碟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌均函,老刑警劉巖亿虽,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異苞也,居然都是意外死亡洛勉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門如迟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來收毫,“玉大人,你說我怎么就攤上這事殷勘〈嗽伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵玲销,是天一觀的道長输拇。 經(jīng)常有香客問我,道長贤斜,這世上最難降的妖魔是什么策吠? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任议慰,我火速辦了婚禮,結(jié)果婚禮上奴曙,老公的妹妹穿的比我還像新娘别凹。我一直安慰自己,他們只是感情好洽糟,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布炉菲。 她就那樣靜靜地躺著,像睡著了一般坤溃。 火紅的嫁衣襯著肌膚如雪拍霜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天薪介,我揣著相機與錄音祠饺,去河邊找鬼。 笑死汁政,一個胖子當(dāng)著我的面吹牛道偷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播记劈,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼勺鸦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了目木?” 一聲冷哼從身側(cè)響起换途,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刽射,沒想到半個月后军拟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡誓禁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年懈息,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片现横。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡漓拾,死狀恐怖阁最,靈堂內(nèi)的尸體忽然破棺而出戒祠,到底是詐尸還是另有隱情,我是刑警寧澤速种,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布姜盈,位于F島的核電站,受9級特大地震影響配阵,放射性物質(zhì)發(fā)生泄漏馏颂。R本人自食惡果不足惜示血,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望救拉。 院中可真熱鬧难审,春花似錦、人聲如沸亿絮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽派昧。三九已至黔姜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒂萎,已是汗流浹背秆吵。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留五慈,地道東北人纳寂。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像泻拦,于是被迫代替她去往敵國和親烈疚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 此篇博客所有源碼均來自JDK 1.8 在上篇博客中介紹了Java四大并發(fā)工具之一的CyclicBarrier聪轿,今天...
    chenssy閱讀 2,921評論 2 27
  • CountDownLatch中count down是倒數(shù)的意思爷肝,latch則是門閂的含義。整體含義可以理解為倒數(shù)的...
    charming_coder閱讀 96,773評論 24 86
  • Countdownlatch 此小節(jié)介紹幾個與鎖有關(guān)的有用工具陆错。 閉鎖(Latch) 閉鎖(Latch):一種同步...
    raincoffee閱讀 324評論 0 1
  • 錯過的是什么灯抛?流逝的是什么?遇見的是什么音瓷?離開你是我傷的最深的最無助的一次对嚼,我想可是我什么都做不了了,你想擁有一個...
    BIU特FAO卡哇伊閱讀 440評論 0 0
  • 四方之城(2)上一章 這天吳憂寧靜的瑜伽時光被好幾個電話打斷绳慎,先是爸爸纵竖,接著是程城的妹妹程蕙,然后是弟弟吳愁杏愤,都是...
    今年九十歲閱讀 676評論 3 5