java并發編程中CountDownLatch和CyclicBarrier的使用

jopen 10年前發布 | 15K 次閱讀 Java開發 Java

在多線程程序設計中,經常會遇到一個線程等待一個或多個線程的場景,遇到這樣的場景應該如何解決?

如果是一個線程等待一個線程,則可以通過await()和notify()來實現;

如果是一個線程等待多個線程,則就可以使用CountDownLatch和CyclicBarrier來實現比較好的控制。

下面來詳細描述下CountDownLatch的應用場景:

例如:百米賽跑:8名運動員同時起跑,由于速度的快慢,肯定有會出現先到終點和晚到終點的情況,而終點有個統計成績的儀器,當所有選手到達終點時,它會統計所有人的成績并進行排序,然后把結果發送到匯報成績的系統。

其實這就是一個CountDownLatch的應用場景:一個線程或多個線程等待其他線程運行達到某一目標后進行自己的下一步工作,而被等待的“其他線程”達到這個目標后繼續自己下面的任務。

這個場景中:

1. 被等待的“其他線程”------>8名運動員

2. 等待“其他線程”的這個線程------>終點統計成績的儀器

那么,如何來通過CountDownLatch來實現上述場景的線程控制和調度呢?

jdk中CountDownLatch類有一個常用的構造方法:CountDownLatch(int count);

                       兩個常用的方法:await()和countdown() 

其中count是一個計數器中的初始化數字,比如初始化的數字是2,當一個線程里調用了countdown(),則這個計數器就減一,當線程調用了 await(),則這個線程就等待這個計數器變為0,當這個計數器變為0時,這個線程繼續自己下面的工作。下面是上述CountDownLatch場景的實現:

Work類(運動員):

    import java.util.concurrent.CountDownLatch;  

    public class Work implements Runnable {  
     private int id;  
     private CountDownLatch beginSignal;  
     private CountDownLatch endSignal;  

     public Work(int id, CountDownLatch begin, CountDownLatch end) {  
      this.id = id;  
      this.beginSignal = begin;  
      this.endSignal = end;  
     }  

     @Override  
     public void run() {  
      try {  
       beginSignal.await();  
       System.out.println("起跑...");  
       System.out.println("work" + id + "到達終點");  
       endSignal.countDown();  
       System.out.println("work" + id + "繼續干其他事情");  
      } catch (InterruptedException e) {  
       // TODO Auto-generated catch block  
       e.printStackTrace();  
      }  
     }  
    }  

Main類(終點統計儀器):

    import java.util.concurrent.CountDownLatch;  

    public class Main {  

     public static void main(String[] args) {  
      CountDownLatch begSignal = new CountDownLatch(1);  
      CountDownLatch endSignal = new CountDownLatch(8);  

      for (int i = 0; i < 8; i++) {  
       new Thread(new Work(i, begSignal, endSignal)).start();  
      }  

      try {  
       begSignal.countDown();  //統一起跑  
       endSignal.await();      //等待運動員到達終點  
       System.out.println("結果發送到匯報成績的系統");  
      } catch (InterruptedException e) {  
       e.printStackTrace();  
      }  
     }  
    }  

下面詳細描述下CyclicBarrier的應用場景:

有四個游戲玩家玩游戲,游戲有三個關卡,每個關卡必須要所有玩家都到達后才能允許通關。

其實這個場景里的玩家中如果有玩家A先到了關卡1,他必須等待其他所有玩家都到達關卡1時才能通過,也就是說線程之間需要互相等待,這和 CountDownLatch的應用場景有區別,CountDownLatch里的線程是到了運行的目標后繼續干自己的其他事情,而這里的線程需要等待其他線程后才能繼續完成下面的工作。

jdk中CyclicBarrier類有兩個常用的構造方法:

1. CyclicBarrier(int parties)

這里的parties也是一個計數器,例如,初始化時parties里的計數是3,于是擁有該CyclicBarrier對象的線程當parties的計數為3時就喚醒,注:這里parties里的計數在運行時當調用CyclicBarrier:await()時,計數就加1,一直加到初始的值

2. CyclicBarrier(int parties, Runnable barrierAction)

這里的parties與上一個構造方法的解釋是一樣的,這里需要解釋的是第二個入參(Runnable barrierAction),這個參數是一個實現Runnable接口的類的對象,也就是說當parties加到初始值時就出發barrierAction的內容。

下面來實現上述的應用場景:

 Player類(玩家類)

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

    public class Player implements Runnable {  

     private CyclicBarrier cyclicBarrier;  
     private int id;  

     public Player(int id, CyclicBarrier cyclicBarrier) {  
      this.cyclicBarrier = cyclicBarrier;  
      this.id = id;  
     }  

     @Override  
     public void run() {  
      try {  
       System.out.println("玩家" + id + "正在玩第一關...");  
       cyclicBarrier.await();  
       System.out.println("玩家" + id + "進入第二關...");  
      } catch (InterruptedException e) {  
       e.printStackTrace();  
      } catch (BrokenBarrierException e) {  
       e.printStackTrace();  
      }  
     }  
    }  

GameBarrier類(關卡類,這里控制玩家必須全部到達第一關結束的關口才能進入第二關)

import java.util.concurrent.CyclicBarrier;  

public class GameBarrier {  

 public static void main(String[] args) {  
  CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {  

   @Override  
   public void run() {  
    System.out.println("所有玩家進入第二關!");  
   }  
  });  

  for (int i = 0; i < 4; i++) {  
   new Thread(new Player(i, cyclicBarrier)).start();  
  }  
 }  
}  
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!