執行緒同步工具(三)等待多個併發事件完成
宣告:本文是《 Java 7 Concurrency Cookbook 》的第三章, 作者: Javier Fernández González 譯者:鄭玉婷
等待多個併發事件完成
Java併發API提供這樣的類,它允許1個或者多個執行緒一直等待,直到一組操作執行完成。 這個類就是CountDownLatch類。它初始一個整數值,此值是執行緒將要等待的運算元。當某個執行緒為了想要執行這些操作而等待時, 它要使用 await()方法。此方法讓執行緒進入休眠直到操作完成。 當某個操作結束,它使用countDown() 方法來減少CountDownLatch類的內部計數器。當計數器到達0時,這個類會喚醒全部使用await() 方法休眠的執行緒們。
在這個指南,你將學習如果使用 CountDownLatch 類來實現 video- conference 系統。 video-conference 系統將等待全部參與者到達後才會開始。
準備
指南中的例子是使用Eclipse IDE 來實現的。如果你使用Eclipse 或者其他的IDE,例如NetBeans,開啟並建立一個新的java任務。
怎麼做呢…
按照這些步驟來實現下面的例子::
//1. 建立一個類名為 Videoconference 並特別實現 Runnable 介面。這個類將實現 video-conference 系統。 public class Videoconference implements Runnable{ //2. 宣告 CountDownLatch 物件名為 controller。 private final CountDownLatch controller; //3. 實現類的建構函式,初始 CountDownLatch 屬性。Videoconference 類接收將要等待的參與者的量為引數。 public Videoconference(int number) { controller=new CountDownLatch(number); } //4. 實現 arrive() 方法。每次有參與者到達都會呼叫此方法。它接收String型別的引數名為 name。 public void arrive(String name){ //5. 首先,它輸出某某引數已經到達。 System.out.printf("%s has arrived.",name); //6. 然後,呼叫CountDownLatch物件的 countDown() 方法。 controller.countDown(); //7. 最後,使用CountDownLatch物件的 getCount() 方法輸出另一條關於還未確定到達的參與者數。 System.out.printf("VideoConference: Waiting for %d participants.\n",controller.getCount()); //8. 實現video-conference 系統的主方法。它是每個Runnable都必須有的 run() 方法。 @Override public void run() { //9. 首先,使用 getCount() 方法來輸出這次video conference的參與值的數量資訊。 System.out.printf("VideoConference: Initialization: %d participants.\n",controller.getCount()); //10. 然後, 使用 await() 方法來等待全部的參與者。由於此法會丟擲 InterruptedException 異常,所以要包含處理程式碼。 try { controller.await(); //11. 最後,輸出資訊表明全部參與者已經到達。 System.out.printf("VideoConference: All the participants have come\n"); System.out.printf("VideoConference: Let's start...\n"); } catch (InterruptedException e) { e.printStackTrace(); } //12. 建立 Participant 類並實現 Runnable 介面。這個類表示每個video conference的參與者。 public class Participant implements Runnable { //13. 宣告一個私有 Videoconference 屬性名為 conference. private Videoconference conference; //14. 宣告一個私有 String 屬性名為 name。 private String name; //15. 實現類的建構函式,初始化那2個屬性。 public Participant(Videoconference conference, String name) { this.conference=conference; this.name=name; } //16. 實現參與者的run() 方法。 @Override public void run() { //17. 首先,讓執行緒隨機休眠一段時間。 long duration=(long)(Math.random()*10); try { TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } //18. 然後,使用Videoconference 物件的arrive() 方法來表明參與者的到達。 conference.arrive(name); //19. 最後,實現例子的 main 類通過建立一個名為 Main 的類併為其新增 main() 方法。 public class Main { public static void main(String[] args) { //20. 建立 Videoconference 物件名為 conference,將等待10個參與者。 Videoconference conference=new Videoconference(10); //21. 建立 Thread 來執行這個 Videoconference 物件並開始執行。 Thread threadConference=new Thread(conference); threadConference.start(); //22. 建立 10個 Participant 物件,為每個物件各建立一個 Thread 物件來執行他們,開始執行全部的執行緒。 for (int i=0; i<10; i++){ Participant p=new Participant(conference, "Participant "+i); Thread t=new Thread(p); t.start(); }
它是怎麼工作的…
CountDownLatch類有3個基本元素:
- 初始值決定CountDownLatch類需要等待的事件的數量。
- await() 方法, 被等待全部事件終結的執行緒呼叫。
- countDown() 方法,事件在結束執行後呼叫。
當建立 CountDownLatch 物件時,物件使用建構函式的引數來初始化內部計數器。每次呼叫 countDown() 方法, CountDownLatch 物件內部計數器減一。當內部計數器達到0時, CountDownLatch 物件喚醒全部使用 await() 方法睡眠的執行緒們。
不可能重新初始化或者修改CountDownLatch物件的內部計數器的值。一旦計數器的值初始後,唯一可以修改它的方法就是之前用的 countDown() 方法。當計數器到達0時, 全部呼叫 await() 方法會立刻返回,接下來任何countDown() 方法的呼叫都將不會造成任何影響。
此方法與其他同步方法有這些不同:
CountDownLatch 機制不是用來保護共享資源或者臨界區。它是用來同步一個或者多個執行多個任務的執行緒。它只能使用一次。像之前解說的,一旦CountDownLatch的計數器到達0,任何對它的方法的呼叫都是無效的。如果你想再次同步,你必須建立新的物件。
以下截圖是例子的執行輸出:
你可以發現最後的參與者到達後,內部計數器一到達0,CountDownLatch物件就叫醒全部的 Videoconference 物件,他們全部輸出資訊表示video conference可以開始了。
更多…
CountDownLatch 類有另一種版本的 await() 方法,它是:
- await(long time, TimeUnit unit): 此方法會休眠直到被中斷; CountDownLatch 內部計數器到達0或者特定的時間過去了。TimeUnit 類包含了:DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, 和 SECONDS.