java併發學習03---CountDownLatch 和 CyclicBarrier

CountDownLatch,顧名思義就是一個倒計時器。(其實Latch的意思是門閂,這個詞的本意是不斷的計數減一,減到0了就打開門閂放行,但一般咱們仍是叫它倒計時器)java

這個倒計時器和咱們傳統意義上的倒計時器並不徹底同樣,這個倒計時器的意思是,一開始規定幾個線程(好比說咱們這裏一開始有10個線程),那麼每一個線程結束以後,會調用倒計時器實例對象的方法,讓它的「計數器」減一,當計數器減到0時,門閂打開放行。多線程

CountDownLatch的簡單原理和應用 :dom

在主線程調用CountDownLatch的實例方法  await()  就可使主線程阻塞起來,經過子線程調用實例方法  countdown()  ,使計數器減1,直到倒計時器減到0以後,門閂打開,主線程能夠繼續執行。ide

簡單來講就是主線程得等全部的子線程都執行完了以後才能執行,這是一個頗有用的線程。函數

對應生活中的場景,我想到《愛情公寓》中有一集,是胡一菲要幫她老弟展博和宛瑜拍婚紗照,一灰同志就如戰場指揮官通常分派它的好友們去處理各類事情,燈光,攝像,攝影棚,頭紗,戒指等等,假如把一灰同志當作是主線程,其他的好友當作是多個子線程的話,那麼一灰同志就得等他們全部的線程都執行完任務以後才能振臂一呼:「開拍」。(雖然最後基本上都沒搞定。。)this

ps:什麼,你不知道一灰同志是誰?----- 胡一灰啦spa

什麼,你說拿情景喜劇裏面的片斷來舉例子簡直是扯淡,好吧,那就再舉一個實際點的例子,好比說我如今在寫的一個多線程的小例子,使用多線程下載圖片(不必定是圖片,也能夠是其餘的資源),思路很簡單,就是先獲取目標文件的長度,分紅幾塊,每一個線程下一塊,都下載好了以後就合併。線程

那麼這裏面有一個關鍵的問題就是如何才能讓主線程等待全部的子線程都下載好了以後再進行合併,CountDownLatch則正好對應了這樣的場景。翻譯

實例代碼嘛,仍是直接用的書上的例子吧(是一個發射火箭的例子,線程池中的每個線程表明火箭發射前的一項技術檢查,可是做者偷懶,就用一個for循環代替了,嘻嘻,我也偷下懶):code

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 演示倒計時器的簡單使用
 */
public class Lesson20_CountDownLatch implements Runnable{
    static final CountDownLatch end = new CountDownLatch(10);
    static final Lesson20_CountDownLatch demo = new Lesson20_CountDownLatch();

    @Override
    public void run() {
        try {
            //模擬檢查任務
            Thread.sleep(new Random().nextInt(10)*1000);
            System.out.println("check complete");
            //每完成一個線程,計數器就減一
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10 ; i++) {
            exec.submit(demo);
        }
        //阻塞主線程,等待檢查
        try {
            end.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //發射火箭
        System.out.println("Fire!");
        exec.shutdown();
    }
}

據說倒計時器不僅僅能夠阻塞一個線程,還能夠阻塞一組線程,你們能夠試試。 

 

 

CyclicBarrier (翻譯過來好像是循環柵欄)

CyclicBarrier和CountDownLatch的做用很類似,但我的感受仍是有所區別的。CyclicBarrier也實現了線程間的相互等待,可是並無阻塞主線程。

仍是上面那個拍婚紗的例子,在上面那個例子中,一灰同志要等全部人都把任務完成才能幫他老弟和宛瑜拍婚紗,其實能夠有兩種辦法實現:

a.   一灰知道她一共分派了5個任務,每有一我的來報道,她就知道解決了一個,還剩4個任務,當全部人都一 一回來以後,她就知道任務都搞定了,這是倒計時器的作法,請注意這個時候主線程是被阻塞住的,也就是說一灰這時啥也不能幹,眨下眼睛都不行。你能夠認爲她睡着了。

b.  一灰跟他們全部人說,大家所有都搞定了再一塊兒回來,不然(此時只見一灰單手將不鏽鋼勺子給掰彎了),你們見狀頓時四散奔逃。。

    因而當悠悠和關谷搞定了攝影師以後便趕快打電話給子喬,「子喬,你頭紗搞到沒」,「早就搞好了,我把我前女朋友叫過來了,她今天結婚」(只見一抹鮮紅的巴掌印留在了子喬的臉上),咳咳。。 好的,就這樣他們一羣人相互聯繫,確認全部人都搞定了本身的任務,這纔回去面見陛下,哦不,是咱們的指揮官一灰同志。

這裏注意,此時,一灰是能夠幹任何事情的,該吃手抓餅吃手抓餅,該喝茶喝茶,時不時還能打個電話催一催進度。

 

那麼,問題來咱們這裏是用一灰來模擬的主線程,那麼他們還沒回來,一灰就開始帶着他老弟去拍婚紗了怎麼辦。

因此說,拍婚紗這個行爲不是由主線程控制的。咱們能夠看一下CyclicBarrier的構造函數,裏面有這麼一條:

 CyclicBarrier(int parties, Runnable barrierAction)

這裏後面這個barrierAction就對應了拍婚紗這個行爲。

那循環柵欄中的循環是什麼意思呢?

這就是它和倒計時器的另一個區別了,倒計時器結束了,門閂打開了就關不上了,而循環柵欄則能夠重複使用,好比一灰讓他們先去買10臺豆漿機,再去買10檯面包機,前後順序不能反,因而他們會相互聯繫確認都買了豆漿機,而後吃個飯慶祝下,哦不是,而後打電話給一灰彙報下,而後再各自去買麪包機,再相互聯繫,是否每一個人都買了,確認以後,再吃個飯慶祝一下。。(注意,這裏的打電話,吃飯慶祝等行爲都是模擬的barrierAction)

  • CyclicBarrier的簡單原理和應用:

      CyclicBarrier與CountDownLatch不一樣,並非主線程拿着一個計時器,而是每一個子線程都持有一個CyclicBarrier的實例,若是有5個線程持有了相同的CyclicBarrier的實例對象,那麼這5個線程就都得確保其餘線程搞定了,我才能繼續執行,上面解釋過,這裏就不解釋了

 偷懶如我,仍是用的書上的例子(什麼,讓我實現本身舉的愛情公寓的例子,什麼,你說啥,我這信號很差,聽不清了,拜拜):

package thread.thread_util;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 循環柵欄
 */
public class Lesson19_CyclicBarrier {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic,String soldierName) {
            this.cyclic = cyclic;
            this.soldier = soldierName;
        }

        @Override
        public void run() {
            //這裏爲了展現柵欄是能夠重複使用的,因此使用了2次await
            try {
                //等到全部士兵到齊
                cyclic.await();

                dowork();
                //等待全部士兵完成工做
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        void dowork() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("soldier" + ": 任務完成");
        }
    }

    //線程完畢時執行的動做
    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;
        public BarrierRun(boolean flagk, int N) {
            this.flag = flag;
            this.N = N;
        }

        @Override
        public void run () {
            if(flag) {
                System.out.println(String.format("司令:【士兵%d個,任務完成】",N));
            } else {
                System.out.println(String.format("司令:【士兵%d個,集合完畢】",N));
                flag = true;
            }
        }
    }
    public static void main(String args[]) throws InterruptedException{
        final int N = 5;
        boolean flag = false;
        CyclicBarrier cyclic = new CyclicBarrier(N,new BarrierRun(flag,N));

        System.out.println("集合隊伍");
        for (int i = 0; i < 5 ; i++) {
            System.out.println("士兵" + i + "報道");
            new Thread(new Soldier(cyclic,"士兵" + (i + 1))).start();
        }
//        System.out.println("主線程over");   你能夠試試,這裏主線程不會被阻塞的
    }
}

 

關於這兩個同步器的使用就告一段落了,如有錯漏之處,請在評論區指出。

相關文章
相關標籤/搜索