一道百度面試題的多種解法

題目

java程序四啰,主進(jìn)程需要等待多個(gè)子進(jìn)程結(jié)束之后再執(zhí)行后續(xù)的代碼译隘,有哪些方案可以實(shí)現(xiàn)?

這個(gè)需求其實(shí)我們?cè)诠ぷ髦薪?jīng)常會(huì)用到梆造,比如用戶下單一個(gè)產(chǎn)品缴守,后臺(tái)會(huì)做一系列的處理,為了提高效率镇辉,每個(gè)處理都可以用一個(gè)線程來執(zhí)行屡穗,所有處理完成了之后才會(huì)返回給用戶下單成功,歡迎大家批評(píng)指正忽肛。

解法

1.join方法

使用Thread的join()等待所有的子線程執(zhí)行完畢村砂,主線程在執(zhí)行,thread.join()把指定的線程加入到當(dāng)前線程屹逛,可以將兩個(gè)交替執(zhí)行的線程合并為順序執(zhí)行的線程础废。比如在線程B中調(diào)用了線程A的join()方法,直到線程A執(zhí)行完畢后罕模,才會(huì)繼續(xù)執(zhí)行線程B评腺。

import java.util.Vector;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Vector<Thread> vector = new Vector<>();
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子線程被執(zhí)行");
                }

            });
            vector.add(childThread);
            childThread.start();
        }
        for(Thread thread : vector) {
            thread.join();
        }
        System.out.println("主線程被執(zhí)行");
    }

執(zhí)行結(jié)果

子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
主線程被執(zhí)行
2.等待多線程完成的CountDownLatch

CountDownLatch的概念

CountDownLatch是一個(gè)同步工具類,用來協(xié)調(diào)多個(gè)線程之間的同步淑掌,或者說起到線程之間的通信(而不是用作互斥的作用)蒿讥。

CountDownLatch能夠使一個(gè)線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行抛腕。使用一個(gè)計(jì)數(shù)器進(jìn)行實(shí)現(xiàn)芋绸。計(jì)數(shù)器初始值為線程的數(shù)量。當(dāng)每一個(gè)線程完成自己任務(wù)后担敌,計(jì)數(shù)器的值就會(huì)減一摔敛。當(dāng)計(jì)數(shù)器的值為0時(shí),表示所有的線程都已經(jīng)完成了任務(wù)柄错,然后在CountDownLatch上等待的線程就可以恢復(fù)執(zhí)行任務(wù)舷夺。
CountDownLatch的用法

CountDownLatch典型用法1:某一線程在開始運(yùn)行前等待n個(gè)線程執(zhí)行完畢苦酱。將CountDownLatch的計(jì)數(shù)器初始化為n new CountDownLatch(n) 售貌,每當(dāng)一個(gè)任務(wù)線程執(zhí)行完畢,就將計(jì)數(shù)器減1 countdownlatch.countDown()疫萤,當(dāng)計(jì)數(shù)器的值變?yōu)?時(shí)颂跨,在CountDownLatch上 await() 的線程就會(huì)被喚醒。一個(gè)典型應(yīng)用場(chǎng)景就是啟動(dòng)一個(gè)服務(wù)時(shí)扯饶,主線程需要等待多個(gè)組件加載完畢恒削,之后再繼續(xù)執(zhí)行池颈。

CountDownLatch典型用法2:實(shí)現(xiàn)多個(gè)線程開始執(zhí)行任務(wù)的最大并行性。注意是并行性钓丰,不是并發(fā)躯砰,強(qiáng)調(diào)的是多個(gè)線程在某一時(shí)刻同時(shí)開始執(zhí)行。類似于賽跑携丁,將多個(gè)線程放到起點(diǎn)琢歇,等待發(fā)令槍響,然后同時(shí)開跑梦鉴。做法是初始化一個(gè)共享的CountDownLatch(1)李茫,將其計(jì)數(shù)器初始化為1,多個(gè)線程在開始執(zhí)行任務(wù)前首先 coundownlatch.await()肥橙,當(dāng)主線程調(diào)用 countDown() 時(shí)魄宏,計(jì)數(shù)器變?yōu)?,多個(gè)線程同時(shí)被喚醒存筏。
CountDownLatch的不足

CountDownLatch是一次性的宠互,計(jì)數(shù)器的值只能在構(gòu)造方法中初始化一次,之后沒有任何機(jī)制再次對(duì)其設(shè)置值方篮,當(dāng)CountDownLatch使用完畢后名秀,它不能再次被使用。

import java.util.Vector;
import java.util.concurrent.CountDownLatch;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(5);
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子線程被執(zhí)行");
                    latch.countDown();
                }

            });

            childThread.start();

        }
        latch.await();//阻塞當(dāng)前線程直到latch中的值
        System.out.println("主線程被執(zhí)行");
    }

}

執(zhí)行結(jié)果:

子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
主線程被執(zhí)行
3.同步屏障CyclicBarrier

這里必須注意藕溅,CylicBarrier是控制一組線程的同步匕得,初始化的參數(shù):5的含義是包括主線程在內(nèi)有5個(gè)線程,所以只能有四個(gè)子線程巾表,這與CountDownLatch是不一樣的汁掠。

countDownLatch和cyclicBarrier有什么區(qū)別呢,他們的區(qū)別:countDownLatch只能使用一次集币,而CyclicBarrier方法可以使用reset()方法重置考阱,所以CyclicBarrier方法可以能處理更為復(fù)雜的業(yè)務(wù)場(chǎng)景。

我曾經(jīng)在網(wǎng)上看到一個(gè)關(guān)于countDownLatch和cyclicBarrier的形象比喻鞠苟,就是在百米賽跑的比賽中若使用 countDownLatch的話沖過終點(diǎn)線一個(gè)人就給評(píng)委發(fā)送一個(gè)人的成績(jī)乞榨,10個(gè)人比賽發(fā)送10次,如果用CyclicBarrier当娱,則只在最后一個(gè)人沖過終點(diǎn)線的時(shí)候發(fā)送所有人的數(shù)據(jù)吃既,僅僅發(fā)送一次,這就是區(qū)別跨细。

package interview;

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

public class Test3 {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        final CyclicBarrier barrier = new CyclicBarrier(5);
        for(int i=0;i<4;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子線程被執(zhí)行");
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            });

            childThread.start();

        }
        barrier.await();//阻塞當(dāng)前線程直到latch中的值
        System.out.println("主線程被執(zhí)行");
    }
}

執(zhí)行結(jié)果:

子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
4.使用yield方法(注意此種方法經(jīng)過親自試驗(yàn)證明并不可靠p幸小)
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子線程被執(zhí)行");

                }

            });

            childThread.start();

        }
        while (Thread.activeCount() > 2) {  //保證前面的線程都執(zhí)行完
            Thread.yield();
        }
        System.out.println("主線程被執(zhí)行");
    }
}

執(zhí)行結(jié)果:

子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行
子線程被執(zhí)行

為何yield方法會(huì)出現(xiàn)這樣的問題?

使當(dāng)前線程從執(zhí)行狀態(tài)(運(yùn)行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))冀惭。cpu會(huì)從眾多的可執(zhí)行態(tài)里選擇震叙,也就是說掀鹅,當(dāng)前也就是剛剛的那個(gè)線程還是有可能會(huì)被再次執(zhí)行到的,并不是說一定會(huì)執(zhí)行其他線程而該線程在下一次中不會(huì)執(zhí)行到了媒楼。

Java線程中有一個(gè)Thread.yield( )方法乐尊,很多人翻譯成線程讓步。顧名思義划址,就是說當(dāng)一個(gè)線程使用了這個(gè)方法之后科吭,它就會(huì)把自己CPU執(zhí)行的時(shí)間讓掉,讓自己或者其它的線程運(yùn)行猴鲫。

打個(gè)比方:現(xiàn)在有很多人在排隊(duì)上廁所对人,好不容易輪到這個(gè)人上廁所了,突然這個(gè)人說:“我要和大家來個(gè)競(jìng)賽拂共,看誰(shuí)先搶到廁所牺弄!”,然后所有的人在同一起跑線沖向廁所宜狐,有可能是別人搶到了势告,也有可能他自己有搶到了。我們還知道線程有個(gè)優(yōu)先級(jí)的問題抚恒,那么手里有優(yōu)先權(quán)的這些人就一定能搶到廁所的位置嗎? 不一定的咱台,他們只是概率上大些,也有可能沒特權(quán)的搶到了俭驮。

yield的本質(zhì)是把當(dāng)前線程重新置入搶CPU時(shí)間的”隊(duì)列”(隊(duì)列只是說所有線程都在一個(gè)起跑線上.并非真正意義上的隊(duì)列)回溺。

5.FutureTast可用于閉鎖,類似于CountDownLatch的作用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test5 {
     public static void main(String[] args) {
        MyThread td = new MyThread();

        //1.執(zhí)行 Callable 方式混萝,需要 FutureTask 實(shí)現(xiàn)類的支持遗遵,用于接收運(yùn)算結(jié)果。
        FutureTask<Integer> result1 = new FutureTask<>(td);
        new Thread(result1).start();
        FutureTask<Integer> result2 = new FutureTask<>(td);
        new Thread(result2).start();
        FutureTask<Integer> result3 = new FutureTask<>(td);
        new Thread(result3).start();

        Integer sum;
        try {
                sum = result1.get();
                sum = result2.get();
                sum = result3.get();
                //這里獲取三個(gè)sum值只是為了同步逸嘀,并沒有實(shí)際意義
                System.out.println(sum);
        } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }  //FutureTask 可用于 閉鎖 類似于CountDownLatch的作用车要,在所有的線程沒有執(zhí)行完成之后這里是不會(huì)執(zhí)行的

        System.out.println("主線程被執(zhí)行");

        }

    }

    class MyThread implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            int sum = 0;
            Thread.sleep(1000);
            for (int i = 0; i <= 10; i++) {
                sum += i;
            }
            System.out.println("子線程被執(zhí)行");
            return sum;
        }
}
6.使用callable+future

Callable+Future最終也是以Callable+FutureTask的形式實(shí)現(xiàn)的。
在這種方式中調(diào)用了: Future future = executor.submit(task);

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test6 {
    public static void main(String[] args) throws InterruptedException, ExecutionException { 
        ExecutorService executor = Executors.newCachedThreadPool(); 
        Task task = new Task(); 
        Future<Integer> future1 = executor.submit(task); 
        Future<Integer> future2 = executor.submit(task);
        //獲取線程執(zhí)行結(jié)果崭倘,用來同步
        Integer result1 = future1.get();
        Integer result2 = future2.get();

        System.out.println("主線程執(zhí)行");
        executor.shutdown();
        } 
}
class Task implements Callable<Integer>{ 
        @Override public Integer call() throws Exception { 
            int sum = 0; 
            //do something; 
            System.out.println("子線程被執(zhí)行");
            return sum; 
            }
}

執(zhí)行結(jié)果:*

子線程被執(zhí)行
子線程被執(zhí)行
主線程執(zhí)行
補(bǔ)充:

1)CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待翼岁,只不過它們側(cè)重點(diǎn)不同:

CountDownLatch一般用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行司光;

而CyclicBarrier一般用于一組線程互相等待至某個(gè)狀態(tài)琅坡,然后這一組線程再同時(shí)執(zhí)行;

另外飘庄,CountDownLatch是不能夠重用的脑蠕,而CyclicBarrier是可以重用的购撼。

2)Semaphore其實(shí)和鎖有點(diǎn)類似跪削,它一般用于控制對(duì)某組資源的訪問權(quán)限谴仙。

CountDownLatch類實(shí)際上是使用計(jì)數(shù)器的方式去控制的,不難想象當(dāng)我們初始化CountDownLatch的時(shí)候傳入了一個(gè)int變量這個(gè)時(shí)候在類的內(nèi)部初始化一個(gè)int的變量碾盐,每當(dāng)我們調(diào)用countDownt()方法的時(shí)候就使得這個(gè)變量的值減1晃跺,而對(duì)于await()方法則去判斷這個(gè)int的變量的值是否為0,是則表示所有的操作都已經(jīng)完成毫玖,否則繼續(xù)等待掀虎。
實(shí)際上如果了解AQS的話應(yīng)該很容易想到可以使用AQS的共享式獲取同步狀態(tài)的方式來完成這個(gè)功能。而CountDownLatch實(shí)際上也就是這么做的付枫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烹玉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阐滩,更是在濱河造成了極大的恐慌二打,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掂榔,死亡現(xiàn)場(chǎng)離奇詭異继效,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)装获,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門瑞信,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人穴豫,你說我怎么就攤上這事凡简。” “怎么了精肃?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵潘鲫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我肋杖,道長(zhǎng)溉仑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任状植,我火速辦了婚禮浊竟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘津畸。我一直安慰自己振定,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布肉拓。 她就那樣靜靜地躺著后频,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卑惜,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天膏执,我揣著相機(jī)與錄音,去河邊找鬼露久。 笑死更米,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毫痕。 我是一名探鬼主播征峦,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼消请!你這毒婦竟也來了栏笆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤臊泰,失蹤者是張志新(化名)和其女友劉穎竖伯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體因宇,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡七婴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了察滑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片打厘。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贺辰,靈堂內(nèi)的尸體忽然破棺而出户盯,到底是詐尸還是另有隱情,我是刑警寧澤饲化,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布莽鸭,位于F島的核電站,受9級(jí)特大地震影響吃靠,放射性物質(zhì)發(fā)生泄漏硫眨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一巢块、第九天 我趴在偏房一處隱蔽的房頂上張望礁阁。 院中可真熱鬧,春花似錦族奢、人聲如沸姥闭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)棚品。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铜跑,已是汗流浹背门怪。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疼进,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓秧廉,卻偏偏與公主長(zhǎng)得像伞广,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疼电,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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