java并發(fā)編程- 5 - 線程池下join()的替代方案:CountDownLatch檬洞、CyclicBarrier

假設(shè)有一個場景:生產(chǎn)汽車分為了三步:制造車身、制造輪子沟饥、組裝車身和輪子添怔。單線程下我們的代碼:

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class CarDemo {

     @Test
    public void madeCarTest1(){
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        int n=5;
        for(int i =0;i<n;i++){
            //單線程
            log.info("開始制造");
            String body=madeBody();
            log.info(body);
            String wheels=madeWheels();
            log.info(wheels);
            String car=madeCar(body,wheels);
            log.info(car);
        }
        stopWatch.stop();
        log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
    }



    private String madeBody(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "車身";
    }

    private String madeWheels(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "輪子";
    }

    private String madeCar(String body,String wheels){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return body+wheels;
    }
}
image.png

單線程下,車身贤旷、輪子和組裝都是穿行的广料,耗時15秒。
咱們改成多線程:

private String body;
    private String wheels;

    @Test
    public void madeCarTest2() throws InterruptedException {
        //多線程
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        int n=5;
        for(int i =0;i<n;i++){


            Thread t1=new Thread(()->{
                body=madeBody();
                log.info(body);
            });
            Thread t2=new Thread(()->{
                wheels=madeWheels();
                log.info(wheels);
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();

            String car=madeCar(body,wheels);
            log.info(car);
        }
        stopWatch.stop();
        log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");


    }

    private String madeBody(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "車身";
    }

    private String madeWheels(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "輪子";
    }

    private String madeCar(String body,String wheels){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return body+wheels;
    }
image.png

改成多線程以后幼驶,制造車身和制造輪子都可以并行進(jìn)行了艾杏,耗時10s 提升了30%。
但是這里有個小缺陷盅藻,我們的線程都是new的购桑,重復(fù)的創(chuàng)建線程太耗資源了,我們應(yīng)該用線程池對線程重復(fù)利用氏淑。但是一旦改成線程池勃蜘,線程就不會真正結(jié)束,所以join()方法就失效了假残。java為我們提供了解決方法:CountDownLatch缭贡。它的原理也很簡單,就是有一個計數(shù)器守问,countDown()方法計數(shù)匀归,await可以讓線程等待,直到計數(shù)器次數(shù)達(dá)到目標(biāo)值:

 Executor executor= Executors.newFixedThreadPool(2);//創(chuàng)建固定線程數(shù)為2的線程池


    @Test
    public void madeCarTest3() throws InterruptedException {
        //多線程
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        int n=5;
        for(int i =0;i<n;i++){
            CountDownLatch countDownLatch=new CountDownLatch(2);//創(chuàng)建一個大小為2的計數(shù)器
            executor.execute(()->{
             
                    body=madeBody();
                    log.info(body);
                    countDownLatch.countDown();
             
            });
            executor.execute(()->{
              
                    wheels=madeWheels();
                    log.info(wheels);
                    countDownLatch.countDown();
               
            });
            countDownLatch.await();
            String car=madeCar(body,wheels);
            log.info(car);
        }
        stopWatch.stop();
        log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
    }


    private String madeBody(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "車身";
    }

    private String madeWheels(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "輪子";
    }

    private String madeCar(String body,String wheels){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return body+wheels;
    }
image.png

可以看到耗帕,比原來快了0.01秒多穆端。
這個程序已經(jīng)是最優(yōu)了的嗎?不仿便,還可以繼續(xù)優(yōu)化体啰。因?yàn)閙adeCar()的時候攒巍,我們還可以繼續(xù)制造下一個的輪子和車身荒勇。你使用CountDownLatch也可以完成這部分工作柒莉,但是java為我們提供更方便的CyclicBarrier沽翔,CyclicBarrier 在達(dá)到期望數(shù)值的時候,回調(diào)一個方法仅偎,并且把數(shù)值重置為初始值:

Vector<String> bodys=new Vector<String>();
    Vector<String> wheelss=new Vector<String>();
    ExecutorService executor1= Executors.newFixedThreadPool(3);//創(chuàng)建固定線程數(shù)為2的線程池

    CyclicBarrier barrier=new CyclicBarrier(3,()->{
        executor1.execute(()->{
            String body=bodys.remove(0);//拿第一個body
            String wheels=wheelss.remove(0);//拿第一個wheels
            String car=madeCar(body,wheels);
            log.info(car);
        });
    });//創(chuàng)建一個計數(shù)器橘沥,當(dāng)數(shù)值達(dá)到2的時候,調(diào)一次
    @Test
    public void madeCarTest4() throws InterruptedException {


        //多線程
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        int n=5;
        for(int i =0;i<n;i++){

            executor1.execute(()->{

                    body=madeBody();
                    bodys.add(body);
                    log.info(body);
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }

            });
            executor1.execute(()->{

                    wheels=madeWheels();
                    wheelss.add(wheels);
                    log.info(wheels);
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }

            });
            try {
                barrier.await();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
        executor1.shutdown();//關(guān)閉線程池痢艺,但是會等線程執(zhí)行完畢
        boolean stat=executor1.awaitTermination(2,TimeUnit.HOURS);//掛起線程介陶,直到線程池關(guān)閉
        if(stat){
            log.info("所有線程執(zhí)行完畢");
        }else {
            log.info("超時或者被中斷");
        }

        stopWatch.stop();
        log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
    }

    private String madeBody(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "車身";
    }

    private String madeWheels(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "輪子";
    }

    private String madeCar(String body,String wheels){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return body+wheels;
    }
image.png

我們改成了這種類似于 生產(chǎn)-消費(fèi) 模式后,速度更快了植酥。因?yàn)樵诮M裝車子的時候弦牡,下一個車子的車身和輪子已經(jīng)開始制造了。

最后驾锰,上面的代碼其實(shí)有個問題:CyclicBarrier 的回調(diào)方法其實(shí)不應(yīng)該跟其他公用一個線程池,應(yīng)該單獨(dú)使用一個長度為1的線程池椭豫。大家想想為什么?
下一章 java并發(fā)編程 - 6 - 并發(fā)容器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喳整,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子框都,更是在濱河造成了極大的恐慌呵晨,老刑警劉巖熬尺,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谓罗,死亡現(xiàn)場離奇詭異,居然都是意外死亡檩咱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蜂筹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芦倒,“玉大人不翩,你說我怎么就攤上這事兵扬】隍穑” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵傲霸,是天一觀的道長眉反。 經(jīng)常有香客問我,道長寸五,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任韧拒,我火速辦了婚禮十性,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘楷掉。我一直安慰自己霞势,他們只是感情好靖诗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布刊橘。 她就那樣靜靜地躺著,像睡著了一般促绵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浓冒,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天尖坤,我揣著相機(jī)與錄音稳懒,去河邊找鬼慢味。 笑死,一個胖子當(dāng)著我的面吹牛纯路,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顶岸,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叫编,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了搓逾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤雏搂,失蹤者是張志新(化名)和其女友劉穎寇损,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矛市,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年而昨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片着憨。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡务嫡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出准谚,到底是詐尸還是另有隱情,我是刑警寧澤柱衔,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布愉棱,位于F島的核電站,受9級特大地震影響羽氮,放射性物質(zhì)發(fā)生泄漏惫恼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一令宿、第九天 我趴在偏房一處隱蔽的房頂上張望腕窥。 院中可真熱鬧,春花似錦簇爆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春粱栖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闹究。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工抄沮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砂代。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓率挣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親椒功。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353