執行緒執行順序——CountDownLatch、CyclicBarrier 、join()、執行緒池

gary-liu發表於2017-03-10

本文主要圍繞一個問題展開:執行緒執行順序,比如某個執行緒在其他執行緒併發執行完畢後最後執行,這裡分別用 CountDownLatch、CyclicBarrier 、join()、執行緒池來實現。

方法一——CyclicBarrier

CyclicBarrier 的字面意思是可迴圈(Cyclic)使用的屏障(Barrier),又叫同步屏障。它可以讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續幹活。執行緒到達屏障的控制通過CyclicBarrier 的 await() 方法實現。

CyclicBarrier 的構造方法有 CyclicBarrier(int parties),其參數列示屏障攔截的執行緒數量,每個執行緒呼叫 await 方法告訴 CyclicBarrier 我已經到達了屏障,然後當前執行緒被阻塞。

CyclicBarrier 還提供一個建構函式 CyclicBarrier(int parties, Runnable barrierAction) ,用於線上程都到達屏障時,優先執行barrierAction 這個 Runnable 物件,然後都到達屏障的執行緒繼續執行。

實現原理

在 CyclicBarrier 的內部定義了一個 ReentrantLock 物件,每當一個執行緒呼叫 CyclicBarrier 的 await 方法時,將剩餘攔截的執行緒數減1,然後判斷剩餘攔截數是否為0,如果不是,進入 Lock 物件的條件佇列等待。如果是則執行 barrierAction 物件的 run 方法,然後將鎖的條件佇列中的所有執行緒放入鎖等待佇列中,這些執行緒會依次的獲取鎖、釋放鎖,接著先從 await 方法返回,再從 CyclicBarrier 的 await 方法中返回。

CyclicBarrier 和 CountDownLatch 比較

  • CountDownLatch 的作用是允許1或N個執行緒等待其他執行緒完成執行;而 CyclicBarrier 則是允許N個執行緒相互等待。

  • CountDownLatch 的計數器無法被重置;CyclicBarrier 的計數器可以被重置後使用,因此它被稱為是迴圈的 barrier。

示例

public class CyclicBarrierPractice {

    static class Worker implements Runnable{

        private String name;
        private CyclicBarrier cyclicBarrier;

        public Worker(String name, CyclicBarrier cyclicBarrier){
            this.name = name;
            this.cyclicBarrier = cyclicBarrier;
        }

        public void run(){
            System.out.println(name + " is working");
            try {
                Thread.sleep(2000);

                //到達屏障出(同步點)
                cyclicBarrier.await();

                //執行緒都到了後繼續向下執行,也可以不要下面程式碼,什麼都不做了
                System.out.println(name + " do other things");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    static class Boss implements Runnable{

        private String name;

        public Boss(String name){
            this.name = name;
        }

        public void run(){
            System.out.println(name + " checks work");

        }
    }

    public static void main(String[] args){

        //其他執行緒都達到屏障後,再執行 boss 執行緒
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Boss("boss"));

        for(int i=0; i<3; i++){
            new Thread(new Worker("worker"+i, cyclicBarrier)).start();
        }
    }
}

執行結果

worker0 is working
worker1 is working
worker2 is working
boss checks work
worker2 do other things
worker0 do other things
worker1 do other things

方法二——join

join() 是 Thread 類的一個方法,join() 方法的作用是等待當前執行緒結束,也即讓“主執行緒”等待“子執行緒”結束之後才能繼續執行。t.join() 方法阻塞呼叫此方法的執行緒 (calling thread),直到執行緒 t完成,此執行緒再繼續(看起來和同步呼叫類似);通常用於在 main 主執行緒內,等待其它執行緒完成後再繼續執行 main 主執行緒。

join 實現

Join 方法實現是通過 wait(Object 提供的方法)。 看原始碼知會進入 while(isAlive()) 迴圈;即只要子執行緒是活的,主執行緒就不停的等待。

示例

用 join 方式實現問題如下,在程式碼中 main 執行緒被阻塞直到 thread1,thread2,thread3 執行完,主執行緒才會順序的執行thread4.

public class JoinPractice {

   static class Worker implements Runnable {

        private String name;

        public Worker(String name){
            this.name = name;
        }

        @Override
        public void run(){
            System.out.println(name + " is working");

        }
    }

    static class Boss implements Runnable{

        private String name;

        public Boss(String name){
            this.name = name;
        }

        @Override
        public void run(){
            System.out.println("boss checks work");
        }
    }

    public static void main(String[] args){

        Worker worker1 = new Worker("worker1");
        Worker worker2 = new Worker("worker2");
        Worker worker3 = new Worker("worker3");
        Boss boss = new Boss("boss");

        Thread thread1 = new Thread(worker1);
        Thread thread2 = new Thread(worker2);
        Thread thread3 = new Thread(worker3);
        Thread thread4 = new Thread(boss);

        thread1.start();
        thread2.start();
        thread3.start();

        try {
            thread1.join();
            thread2.join();
            thread3.join();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread4.start();
    }

}

執行結果

worker2 is working
worker1 is working
worker3 is working
boss checks work

方法三——CountDownLatch

Java 的 util.concurrent 包裡面的 CountDownLatch 其實可以把它看作一個計數器(倒數計時鎖),只不過這個計數器的操作是原子操作,同時只能有一個執行緒去操作這個計數器,也就是同時只能有一個執行緒去減這個計數器裡面的值。

你可以向 CountDownLatch 物件設定一個初始的數字作為計數值,任何呼叫這個物件上的 await() 方法都會阻塞,直到這個計數器的計數值被其他的執行緒減為 0 為止。

使用場景

CountDownLatch 的一個非常典型的應用場景是:有一個任務想要往下執行,但必須要等到其他的任務執行完畢後才可以繼續往下執行。假如我們這個想要繼續往下執行的任務呼叫一個 CountDownLatch 物件的 await() 方法,其他的任務執行完自己的任務後呼叫同一個CountDownLatch 物件上的 countDown() 方法,這個呼叫 await() 方法的任務將一直阻塞等待,直到這個CountDownLatch物件的計數值減到 0 為止。

示例

舉個例子,有三個工人在為老闆幹活,這個老闆有一個習慣,就是當三個工人把一天的活都幹完了的時候,他就來檢查所有工人所幹的活。記住這個條件:三個工人先全部幹完活,老闆才檢查。所以在這裡用 Java 程式碼設計兩個類,Worker 代表工人,Boss 代表老闆,程式碼使用了內部類實現。


public class OrderThreadExecute {

    class Worker implements Runnable {
        private CountDownLatch downLatch;
        private String name;

        public Worker(CountDownLatch downLatch, String name) {
            this.downLatch = downLatch;
            this.name = name;
        }

        @Override
        public void run() {
            this.doWork();
            try {
                TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            } catch (InterruptedException ie) {
            }
            System.out.println(this.name + "活幹完了!");
            this.downLatch.countDown();
        }

        private void doWork() {
            System.out.println(this.name + "正在幹活...");
        }

    }

    class Boss implements Runnable {
        private CountDownLatch downLatch;

        public Boss(CountDownLatch downLatch) {
            this.downLatch = downLatch;
        }

        @Override
        public void run() {
            System.out.println("老闆正在等所有的工人幹完活......");
            try {
                this.downLatch.await();
            } catch (InterruptedException e) {
            }
            System.out.println("工人活都幹完了,老闆開始檢查了!");
        }

    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(3);

        OrderThreadExecute orderThread = new OrderThreadExecute();

        Worker w1 = orderThread.new Worker(latch, "張三");
        Worker w2 = orderThread.new Worker(latch, "李四");
        Worker w3 = orderThread.new Worker(latch, "王二");

        Boss boss = orderThread.new Boss(latch);

        executor.execute(boss);
        executor.execute(w3);
        executor.execute(w2);
        executor.execute(w1);

        executor.shutdown();

    }

}

執行結果

老闆正在等所有的工人幹完活......
王二正在幹活...
李四正在幹活...
張三正在幹活...
李四活幹完了!
王二活幹完了!
張三活幹完了!
工人活都幹完了,老闆開始檢查了!

CountDownLatch 與 join 比較

呼叫thread.join() 方法必須等thread 執行完畢,當前執行緒才能繼續往下執行,而CountDownLatch通過計數器提供了更靈活的控制,只要檢測到計數器為0當前執行緒就可以往下執行而不用管相應的thread是否執行完畢。

具體比較見文章:http://blog.csdn.net/nyistzp/article/details/51444487

方法四——執行緒池

當執行緒池的執行緒全部執行完畢後再執行主執行緒,示例程式碼如下。

public class ExecuteOrderPractice {

    public void orderPractice(){
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for(int i = 0; i < 5; i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + " do something");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }

        executorService.shutdown();

        while(true){
            if(executorService.isTerminated()){
                System.out.println("Finally do something ");
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        new ExecuteOrderPractice().orderPractice();

    }
}

執行結果

pool-1-thread-1 do something
pool-1-thread-3 do something
pool-1-thread-2 do something
pool-1-thread-3 do something
pool-1-thread-1 do something
Finally do something 

參考資料

java 多執行緒 CountDownLatch與join()方法區別
CountDownLatch使用例項
Java如何判斷執行緒池所有任務是否執行完畢

相關文章