JAVA並發包中有三個類用於同步一批線程的行為,分別是閉鎖(Latch),信號燈(Semaphore)和柵欄(CyclicBarrier)。本貼主要說明閉鎖(Latch)和柵欄(CyclicBarrier)。
1. 閉鎖(Latch)
閉鎖(Latch) —— 確保多個線程在完成各自事務后,才會打開繼續執行后面的內容,否則一直等待。
計數器閉鎖(CountDownLatch) —— 是JDK5+ 里面閉鎖的一個實現,允許一個或多個線程等待某個事件的發生。CountDownLatch 有個正數的計數器,countDown(); 對計數器做減法操作,await(); 等待計數器 = 0。所有await的線程都會阻塞,直到計數器為0或者等待線程中斷或者超時。
1 public static void main(String[] args) throws InterruptedException { 2 // 申明,等待事件數量 5次 3 CountDownLatch await = new CountDownLatch(5); 4 5 // 依次創建並啟動處於等待狀態的5個MyRunnable線程 6 for (int i = 1; i < 6; ++i) { 7 new Thread(new MyRunnable(await, i)).start(); 8 } 9 10 System.out.println("等待線程開始工作......"); 11 await.await(); 12 System.out.println("結束!"); 13 }
1 public static class MyRunnable implements Runnable { 2 3 private final CountDownLatch await; 4 private final int num; 5 6 public MyRunnable(CountDownLatch await, int num) { 7 this.await = await; 8 this.num = num; 9 } 10 11 public void run() { 12 try { 13 System.out.println("線程"+num+"執行完畢。"); 14 await.countDown(); // 當前事件執行完畢,計數 -1 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 }
運行結果:
等待線程開始工作......
線程1執行完畢。
線程2執行完畢。
線程3執行完畢。
線程4執行完畢。
線程5執行完畢。
結束!
流程如圖所示:
圖1 - CountDownLatch 處理流程
2. 柵欄(CyclicBarrier)
柵欄類似於閉鎖,它能阻塞一組線程直到某個事件發生。 柵欄與閉鎖的關鍵區別在於,所有的線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其他線程。
場景: 比如甲乙丙三人一把椅子,甲做椅子腿,乙做椅子面,丙做椅子靠背。等3人都做成后,就可以組裝成椅子了。這是一種並行迭代,將一個問題分成很多子問題,當一系列的子問題都解決之后(所有子問題線程都已經await(); ),此時將柵欄打開,所有子問題線程被釋放,而柵欄位置可以留着下次使用。
示例如下:
1 public static void main(String[] args) throws InterruptedException { 2 // 申明,等待線程數量 3次 3 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); 4 5 // 依次創建並啟動處於等待狀態的3個MyRunnable2線程 6 new Thread(new ChairRunnable(cyclicBarrier, "椅子腿")).start(); 7 new Thread(new ChairRunnable(cyclicBarrier, "椅子面")).start(); 8 new Thread(new ChairRunnable(cyclicBarrier, "椅子背")).start(); 9 }
1 public static class ChairRunnable implements Runnable { 2 private final CyclicBarrier cyclicBarrier; 3 private final String event; 4 5 public ChairRunnable(CyclicBarrier cyclicBarrier, String event) { 6 this.cyclicBarrier = cyclicBarrier; 7 this.event = event; 8 } 9 10 public void run() { 11 try { 12 System.out.println("開始做【" + event + "】。"); 13 Thread.sleep(new Random().nextInt(10000)); 14 cyclicBarrier.await(); // 等待其他線程完成 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } catch (BrokenBarrierException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("【" + event + "】做好了, 我們來一起組裝吧!"); 21 } 22 }
運行結果:
開始做【椅子腿】。
開始做【椅子背】。
開始做【椅子面】。
【椅子面】做好了, 我們來一起組裝吧!
【椅子腿】做好了, 我們來一起組裝吧!
【椅子背】做好了, 我們來一起組裝吧!
流程如下圖所示:
圖2 - CyclicBarrier 處理流程