Java:多執行緒等待所有執行緒結束(CountDownLatch/CyclicBarrier) .

yangxi_001發表於2013-11-29

本文主要是參考官方文件做一學習用途。

官方連結:

http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/CountDownLatch.html
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html 

多執行緒設計過程中,經常會遇到需要等待其它執行緒結束以後再做其他事情的情況,比如多執行緒下載檔案,每個執行緒都會下載檔案的一部分,在所有執行緒結束以後,需要將各部分再次拼接成一個完整的檔案。

有幾種方案:

1.在主執行緒中設定一自定義全域性計數標誌,在工作執行緒完成時,計數減一。主執行緒偵測該標誌是否為0,一旦為0,表示所有工作執行緒已經完成。

2.使用Java標準的類CountDownLatch來完成這項工作,原理是一樣的,計數。

CountDownLatch

CountDownLatch 初始化設定count,即等待(await)count個執行緒或一個執行緒count次計數,通過工作執行緒來countDown計數減一,直到計數為0,await阻塞結束。

設定的count不可更改,如需要動態設定計數的執行緒數,可以使用CyclicBarrier.

下面的例子,所有的工作執行緒中準備就緒以後,並不是直接執行,而是等待主執行緒的訊號後再執行具體的操作。

  1. package com.example.multithread;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4.   
  5. class Driver  
  6. {  
  7.     private static final int TOTAL_THREADS = 10;  
  8.     private final CountDownLatch mStartSignal = new CountDownLatch(1);  
  9.     private final CountDownLatch mDoneSignal = new CountDownLatch(TOTAL_THREADS);  
  10.   
  11.     void main()  
  12.     {  
  13.         for (int i = 0; i < TOTAL_THREADS; i++)  
  14.         {  
  15.             new Thread(new Worker(mStartSignal, mDoneSignal, i)).start();  
  16.         }  
  17.         System.out.println("Main Thread Now:" + System.currentTimeMillis());  
  18.         doPrepareWork();// 準備工作   
  19.         mStartSignal.countDown();// 計數減一為0,工作執行緒真正啟動具體操作   
  20.         doSomethingElse();//做點自己的事情   
  21.         try  
  22.         {  
  23.             mDoneSignal.await();// 等待所有工作執行緒結束   
  24.         }  
  25.         catch (InterruptedException e)  
  26.         {  
  27.             // TODO Auto-generated catch block   
  28.             e.printStackTrace();  
  29.         }  
  30.         System.out.println("All workers have finished now.");  
  31.         System.out.println("Main Thread Now:" + System.currentTimeMillis());  
  32.     }  
  33.   
  34.     void doPrepareWork()  
  35.     {  
  36.         System.out.println("Ready,GO!");  
  37.     }  
  38.   
  39.     void doSomethingElse()  
  40.     {  
  41.         for (int i = 0; i < 100000; i++)  
  42.         {  
  43.             ;// delay   
  44.         }  
  45.         System.out.println("Main Thread Do something else.");  
  46.     }  
  47. }  
  48.   
  49. class Worker implements Runnable  
  50. {  
  51.     private final CountDownLatch mStartSignal;  
  52.     private final CountDownLatch mDoneSignal;  
  53.     private final int mThreadIndex;  
  54.   
  55.     Worker(final CountDownLatch startSignal, final CountDownLatch doneSignal,  
  56.             final int threadIndex)  
  57.     {  
  58.         this.mDoneSignal = doneSignal;  
  59.         this.mStartSignal = startSignal;  
  60.         this.mThreadIndex = threadIndex;  
  61.     }  
  62.   
  63.     @Override  
  64.     public void run()  
  65.     {  
  66.         // TODO Auto-generated method stub   
  67.         try  
  68.         {  
  69.             mStartSignal.await();// 阻塞,等待mStartSignal計數為0執行後面的程式碼   
  70.                                     // 所有的工作執行緒都在等待同一個啟動的命令   
  71.             doWork();// 具體操作   
  72.             System.out.println("Thread " + mThreadIndex + " Done Now:"  
  73.                     + System.currentTimeMillis());  
  74.             mDoneSignal.countDown();// 完成以後計數減一   
  75.         }  
  76.         catch (InterruptedException e)  
  77.         {  
  78.             // TODO Auto-generated catch block   
  79.             e.printStackTrace();  
  80.         }  
  81.     }  
  82.   
  83.     public void doWork()  
  84.     {  
  85.         for (int i = 0; i < 1000000; i++)  
  86.         {  
  87.             ;// 耗時操作   
  88.         }  
  89.         System.out.println("Thread " + mThreadIndex + ":do work");  
  90.     }  
  91. }  
  92.   
  93. public class CountDownLatchTest  
  94. {  
  95.     public static void main(String[] args)  
  96.     {  
  97.         // TODO Auto-generated method stub   
  98.         new Driver().main();  
  99.     }  
  100.   
  101. }  

通過Executor啟動執行緒:

  1. class CountDownLatchDriver2  
  2. {  
  3.     private static final int TOTAL_THREADS = 10;  
  •     private final CountDownLatch mDoneSignal = new CountDownLatch(TOTAL_THREADS);  

  •   
  •     void main()  
  •     {  
  •         System.out.println("Main Thread Now:" + System.currentTimeMillis());  
  •         doPrepareWork();// 準備工作   
  •   
  •         Executor executor = Executors.newFixedThreadPool(TOTAL_THREADS);  
  •         for (int i = 0; i < TOTAL_THREADS; i++)  
  •         {  
  •             // 通過內建的執行緒池維護建立的執行緒   
  •             executor.execute(new RunnableWorker(mDoneSignal, i));  
  •         }  
  •         doSomethingElse();// 做點自己的事情   
  •         try  
  •         {  
  •             mDoneSignal.await();// 等待所有工作執行緒結束   
  •         }  
  •         catch (InterruptedException e)  
  •         {  
  •             // TODO Auto-generated catch block   
  •             e.printStackTrace();  
  •         }  
  •         System.out.println("All workers have finished now.");  
  •         System.out.println("Main Thread Now:" + System.currentTimeMillis());  
  •     }  
  •   
  •     void doPrepareWork()  
  •     {  
  •         System.out.println("Ready,GO!");  
  •     }  
  •   
  •     void doSomethingElse()  
  •     {  
  •         for (int i = 0; i < 100000; i++)  
  •         {  
  •             ;// delay   
  •         }  
  •         System.out.println("Main Thread Do something else.");  
  •     }  
  • }  
  •   
  • class RunnableWorker implements Runnable  
  • {  
  •   
  •     private final CountDownLatch mDoneSignal;  
  •     private final int mThreadIndex;  
  •   
  •     RunnableWorker(final CountDownLatch doneSignal, final int threadIndex)  
  •     {  
  •         this.mDoneSignal = doneSignal;  
  •         this.mThreadIndex = threadIndex;  
  •     }  
  •   
  •     @Override  
  •     public void run()  
  •     {  
  •         // TODO Auto-generated method stub   
  •   
  •         doWork();// 具體操作   
  •         System.out.println("Thread " + mThreadIndex + " Done Now:"  
  •                 + System.currentTimeMillis());  
  •         mDoneSignal.countDown();// 完成以後計數減一   
  •                                 // 計數為0時,主執行緒接觸阻塞,繼續執行其他任務   
  •         try  
  •         {  
  •             // 可以繼續做點其他的事情,與主執行緒無關了   
  •             Thread.sleep(5000);  
  •             System.out.println("Thread " + mThreadIndex  
  •                     + " Do something else after notifing main thread");  
  •   
  •         }  
  •         catch (InterruptedException e)  
  •         {  
  •             // TODO Auto-generated catch block   
  •             e.printStackTrace();  
  •         }  
  •   
  •     }  
  •   
  •     public void doWork()  
  •     {  
  •         for (int i = 0; i < 1000000; i++)  
  •         {  
  •             ;// 耗時操作   
  •         }  
  •         System.out.println("Thread " + mThreadIndex + ":do work");  
  •     }  
  • }  

輸出:

Main Thread Now:1359959480786
Ready,GO!
Thread 0:do work
Thread 0 Done Now:1359959480808
Thread 1:do work
Thread 1 Done Now:1359959480811
Thread 2:do work
Thread 2 Done Now:1359959480813
Main Thread Do something else.
Thread 3:do work
Thread 3 Done Now:1359959480825
Thread 5:do work
Thread 5 Done Now:1359959480827
Thread 7:do work
Thread 7 Done Now:1359959480829
Thread 9:do work
Thread 9 Done Now:1359959480831
Thread 4:do work
Thread 4 Done Now:1359959480833
Thread 6:do work
Thread 6 Done Now:1359959480835
Thread 8:do work
Thread 8 Done Now:1359959480837
All workers have finished now.
Main Thread Now:1359959480838
Thread 0 Do something else after notifing main thread
Thread 1 Do something else after notifing main thread
Thread 2 Do something else after notifing main thread
Thread 3 Do something else after notifing main thread
Thread 9 Do something else after notifing main thread
Thread 7 Do something else after notifing main thread
Thread 5 Do something else after notifing main thread
Thread 4 Do something else after notifing main thread
Thread 6 Do something else after notifing main thread
Thread 8 Do something else after notifing main thread
CyclicBarrier
使用CyclickBarrier的例子:
  1. class WalkTarget  
  2. {  
  3.     private final int mCount = 5;  
  •     private final CyclicBarrier mBarrier;  
  •     ExecutorService mExecutor;  
  •   
  •     class BarrierAction implements Runnable  
  •     {  
  •         @Override  
  •         public void run()  
  •         {  
  •             // TODO Auto-generated method stub   
  •             System.out.println("所有執行緒都已經完成任務,計數達到預設值");  
  •             //mBarrier.reset();//恢復到初始化狀態          
  •               
  •         }  
  •     }  
  •   
  •     WalkTarget()  
  •     {  
  •         //初始化CyclicBarrier   
  •         mBarrier = new CyclicBarrier(mCount, new BarrierAction());  
  •         mExecutor = Executors.newFixedThreadPool(mCount);  
  •   
  •         for (int i = 0; i < mCount; i++)  
  •         {  
  •             //啟動工作執行緒   
  •             mExecutor.execute(new Walker(mBarrier, i));  
  •         }  
  •     }  
  • }  
  •   
  • //工作執行緒   
  • class Walker implements Runnable  
  • {  
  •     private final CyclicBarrier mBarrier;  
  •     private final int mThreadIndex;  
  •   
  •     Walker(final CyclicBarrier barrier, final int threadIndex)  

  •     {  
  •         mBarrier = barrier;  
  •         mThreadIndex = threadIndex;  
  •     }  
  •   
  •     @Override  
  •     public void run()  
  •     {  
  •         // TODO Auto-generated method stub   
  •         System.out.println("Thread " + mThreadIndex + " is running...");  
  •         // 執行任務   
  •         try  
  •         {  
  •             TimeUnit.MILLISECONDS.sleep(5000);  
  •             // do task   
  •         }  
  •         catch (InterruptedException e)  
  •         {  
  •             // TODO Auto-generated catch block   
  •             e.printStackTrace();  
  •         }  
  •   
  •         // 完成任務以後,等待其他執行緒完成任務   
  •         try  
  •         {  
  •             mBarrier.await();  
  •         }  
  •         catch (InterruptedException e)  
  •         {  
  •             // TODO Auto-generated catch block   
  •             e.printStackTrace();  
  •         }  
  •         catch (BrokenBarrierException e)  
  •         {  
  •             // TODO Auto-generated catch block   
  •             e.printStackTrace();  
  •         }  
  •         // 其他執行緒任務都完成以後,阻塞解除,可以繼續接下來的任務   
  •         System.out.println("Thread " + mThreadIndex + " do something else");  
  •     }  
  •   
  • }  
  •   
  • public class CountDownLatchTest  
  • {  
  •     public static void main(String[] args)  
  •     {  
  •         // TODO Auto-generated method stub   
  •         //new CountDownLatchDriver2().main();   
  •         new WalkTarget();  
  •     }  
  •   
  • }  

輸出(注意,只有所有的執行緒barrier.await之後才能繼續執行其他的操作):

Thread 0 is running... Thread 2 is running... Thread 3 is running... Thread 1 is running... Thread 4 is running... 所有執行緒都已經完成任務,計數達到預設值 Thread 4 do something else Thread 0 do something else Thread 2 do something else Thread 3 do something else Thread 1 do something else

CountDownLatch和CyclicBarrier簡單比較:

 

CountDownLatch

CyclicBarrier

軟體包

java.util.concurrent

java.util.concurrent

適用情景

主執行緒等待多個工作執行緒結束

多個執行緒之間互相等待,直到所有執行緒達到一個障礙點(Barrier point)

主要方法

CountDownLatch(int count) (主執行緒呼叫)

初始化計數

CountDownLatch.await (主執行緒呼叫)

阻塞,直到等待計數為0解除阻塞

CountDownLatch.countDown

計數減一(工作執行緒呼叫)

CyclicBarrier(int parties, Runnable barrierAction) //初始化參與者數量和障礙點執行Action,Action可選。由主執行緒初始化

CyclicBarrier.await() //由參與者呼叫

阻塞,直到所有執行緒達到屏障點

等待結束

各執行緒之間不再互相影響,可以繼續做自己的事情。不再執行下一個目標工作。

在屏障點達到後,允許所有執行緒繼續執行,達到下一個目標。可以重複使用CyclicBarrier

異常

 

如果其中一個執行緒由於中斷,錯誤,或超時導致永久離開屏障點,其他執行緒也將丟擲異常。

其他

 

如果BarrierAction不依賴於任何Party中的所有執行緒,那麼在任何party中的一個執行緒被釋放的時候,可以直接執行這個Action。

If(barrier.await()==2)

{

//do action

}


相關文章