1. 程式人生 > >java併發學習03---CountDownLatch 和 CyclicBarrier

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

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

這個倒計時器和我們傳統意義上的倒計時器並不完全一樣,這個倒計時器的意思是,一開始規定幾個執行緒(比如說我們這裡一開始有10個執行緒),那麼每個執行緒結束之後,會呼叫倒計時器例項物件的方法,讓它的“計數器”減一,當計數器減到0時,門閂開啟放行。

CountDownLatch的簡單原理和應用 :

在主執行緒呼叫CountDownLatch的例項方法  await()  就可以使主執行緒阻塞起來,通過子執行緒呼叫例項方法  countdown() 

,使計數器減1,直到倒計時器減到0之後,門閂開啟,主執行緒可以繼續執行。

簡單來說就是主執行緒得等所有的子執行緒都執行完了之後才能執行,這是一個很有用的執行緒。

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

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

什麼,你說拿情景喜劇裡面的片段來舉例子簡直是扯淡,好吧,那就再舉一個實際點的例子,比如說我現在在寫的一個多執行緒的小例子,使用多執行緒下載圖片(不一定是圖片,也可以是其他的資源),思路很簡單,就是先獲取目標檔案的長度,分成幾塊,每個執行緒下一塊,都下載好了之後就合併。

那麼這裡面有一個關鍵的問題就是如何才能讓主執行緒等待所有的子執行緒都下載好了之後再進行合併,CountDownLatch則正好對應了這樣的場景。

例項程式碼嘛,還是直接用的書上的例子吧(是一個發射火箭的例子,執行緒池中的每一個執行緒代表火箭發射前的一項技術檢查,但是作者偷懶,就用一個for迴圈代替了,嘻嘻,我也偷下懶):

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");   你可以試試,這裡主執行緒不會被阻塞的
    }
}

 

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