Java并發(fā)編程——CountDownLatch

1. 簡介

在上篇中我們介紹了SyclicBarrier類的使用,通過SyclicBarrier我們可以完成一些分批執(zhí)行匯總的任務昨忆,而此次介紹的CountDownLatch則是實現(xiàn)類似“倒計時”的功能室叉。

2. Api分析

CountDownLatch源碼很簡潔,它提供兩個典型的方法來實現(xiàn)“倒計時功能”。CountDownLatch在構(gòu)造方法中指定門閂鎖latch的個數(shù)术裸,當latch的個數(shù)為0的時候則喚醒所有等待狀態(tài)下的線程旭蠕。

  • countDown():通過調(diào)用countDown方法實現(xiàn)門閂鎖-1的效果停团。
  • await():讓當前線程進入等待。
  • getCount():獲取門閂鎖的個數(shù)掏熬。

下面我們通過一個示例代碼來看一下:

public class MyTest {
    static CountDownLatch countDownLatch;
    public static void main(String[]args){
        countDownLatch = new CountDownLatch(3);
        for(int i=0;i<3;i++){
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "--到達車門");
                    countDownLatch.countDown();
                    System.out.println(Thread.currentThread().getName() + "--已登車");
                }
            }).start();
        }
        
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--登車完畢");
    }
}

在示例代碼中佑稠,通過構(gòu)造函數(shù)創(chuàng)建一個指定latch個數(shù)為3的CountDownLatch的方法,同時創(chuàng)建了三個線程旗芬,在每個線程的內(nèi)部分別調(diào)用一次countDown()方法舌胶,最后在主線程中調(diào)用await方法進行等待阻塞,最后完成輸出疮丛。
執(zhí)行結(jié)果:

Thread-2--到達車門
Thread-2--已登車
Thread-0--到達車門
Thread-0--已登車
Thread-1--到達車門
main--登車完畢
Thread-1--已登車

從上面可以得知CountDownLatch以下特點:

  1. 線程中執(zhí)行countDown方法后能繼續(xù)執(zhí)行后續(xù)代碼幔嫂,線程不會阻塞等待;
  2. latch個數(shù)為0的時候會立即喚醒await等待的線程这刷;

3. CountDownLatch源碼解析

結(jié)合上面對CountDownLatch功能的描述婉烟,假如讓我們實現(xiàn)一個這樣的功能,我們首先想到的思路應該是在工具類中維持一個Count計數(shù)變量暇屋,然后維持對該變量的判斷似袁。那么我們來看看CountDownLatch是怎么實現(xiàn)的。

public class CountDownLatch {
    /**
     * 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;
            }
        }
    }

    private final Sync sync;

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

    /**
     * 讓當前線程進行等待咐刨,直到鎖個數(shù)為0或者當前線程interrupt
     *  如果當前鎖latch個數(shù)為0昙衅,則立即返回
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

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

    /**
     * 減少latch鎖,當latch鎖個數(shù)為0的時候釋放所有等待線程定鸟。
     */
    public void countDown() {
        sync.releaseShared(1);
    }

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

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

首先從構(gòu)造方法來看而涉,CountDownLatch提供了有參構(gòu)造函數(shù):

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

這里的Sync類是CountDownLatch內(nèi)部集成AQS實現(xiàn)的一個內(nèi)部類。

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

在Sync的構(gòu)造函數(shù)中联予,調(diào)用的是AQS的setState方法修改state值啼县,將state值修改為count值。

通過上面的介紹沸久,對于CountDownLatch類來說季眷,使用最多的就是countDown和await方法。

countDown()方法

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

/**AQS類中定義的該方法*/
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

在countDown方法內(nèi)部是調(diào)用Sync的releaseShared方法卷胯,嘗試釋放公共鎖狀態(tài)子刮。

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;
    }
}

采用自旋的方式,獲取當前state值窑睁,如果state值為0就返回false挺峡,即釋放失敗葵孤。反之則state-1,通過CAS操作成功橱赠,返回nextc == 0尤仍;當最后通過咨詢的方式返回true的時候(state ==0),則執(zhí)行releaseShared方法中的doReleaseShared()方法狭姨,在doReleaseShared()方法內(nèi)部通過unparkSuccessor()方法喚醒阻塞的線程開始執(zhí)行吓著。

await()方法

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

/***AQS中的方法*/
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

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

這里調(diào)用acquireSharedInterruptibly()方法申請鎖,如果鎖被占有state送挑!=0,則通過doAcquireSharedInterruptibly()進行鎖申請暖眼。

4. CountDownLatch的使用場景

  • 確保某個計算在其需要的所有資源都被初始化之后才繼續(xù)執(zhí)行惕耕。
  • 確保某個服務在其依賴的所有其他服務都已啟動后才啟動。
  • 等待知道某個操作的所有者都就緒在繼續(xù)執(zhí)行诫肠。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末司澎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子栋豫,更是在濱河造成了極大的恐慌挤安,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丧鸯,死亡現(xiàn)場離奇詭異蛤铜,居然都是意外死亡,警方通過查閱死者的電腦和手機丛肢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門围肥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜂怎,你說我怎么就攤上這事穆刻。” “怎么了杠步?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵氢伟,是天一觀的道長。 經(jīng)常有香客問我幽歼,道長朵锣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任试躏,我火速辦了婚禮猪勇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颠蕴。我一直安慰自己泣刹,他們只是感情好助析,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椅您,像睡著了一般外冀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掀泳,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天雪隧,我揣著相機與錄音,去河邊找鬼员舵。 笑死脑沿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的马僻。 我是一名探鬼主播庄拇,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼韭邓!你這毒婦竟也來了措近?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤女淑,失蹤者是張志新(化名)和其女友劉穎瞭郑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭你,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡屈张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袱巨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袜茧。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瓣窄,靈堂內(nèi)的尸體忽然破棺而出笛厦,到底是詐尸還是另有隱情,我是刑警寧澤俺夕,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布裳凸,位于F島的核電站,受9級特大地震影響劝贸,放射性物質(zhì)發(fā)生泄漏姨谷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一映九、第九天 我趴在偏房一處隱蔽的房頂上張望梦湘。 院中可真熱鬧,春花似錦、人聲如沸捌议。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓣颅。三九已至倦逐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宫补,已是汗流浹背檬姥。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粉怕,地道東北人健民。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像贫贝,于是被迫代替她去往敵國和親荞雏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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