CountDownLatch、CyclicBarrier、Semaphore共同之處與區別以及各自使用場景

轉載自:http://blog.csdn.net/jackyechina/article/details/52931453

區別

  • CountDownLatch 使一個線程A或是組線程A等待其它線程執行完畢後,一個線程A或是組線程A才繼續執行。CyclicBarrier:一組線程使用await()指定barrier,所有線程都到達各自的barrier後,再同時執行各自barrier下面的代碼。Semaphore:是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源
  • CountDownLatch是減計數方式,計數==0時釋放所有等待的線程;CyclicBarrier是加計數方式,計數達到構造方法中參數指定的值時釋放所有等待的線程。Semaphore,每次semaphore.acquire(),獲取一個資源,每次semaphore.acquire(n),獲取n個資源,當達到semaphore 指定資源數量時就不能再訪問線程處於阻塞,必須等其它線程釋放資源,semaphore.relase()每次資源一個資源,semaphore.relase(n)每次資源n個資源。
  • CountDownLatch當計數到0時,計數無法被重置;CyclicBarrier計數達到指定值時,計數置爲0重新開始。
  • CountDownLatch每次調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響;CyclicBarrier只有一個await()方法,調用await()方法計數加1,若加1後的值不等於構造方法的值,則線程阻塞。
  • CountDownLatch、CyclikBarrier、Semaphore 都有一個int類型參數的構造方法。CountDownLatch、CyclikBarrier這個值作爲計數用,達到該次數即釋放等待的線程,而Semaphore 中所有acquire獲取到的資源達到這個數,會使得其它線程阻塞。

共同

  • CountDownLatch與CyclikBarrier兩者的共同點是都具有await()方法,並且執行此方法會引起線程的阻塞,達到某種條件才能繼續執行(這種條件也是兩者的不同)。Semaphore,acquire方獲取的資源達到最大數量時,線程再次acquire獲取資源時,也會使線程處於阻塞狀態。CountDownLatch與CyclikBarrier兩者的共同點是都具有await()方法,並且執行此方法會引起線程的阻塞,達到某種條件才能繼續執行(這種條件也是兩者的不同)。Semaphore,acquire方獲取的資源達到最大數量時,線程再次acquire獲取資源時,也會使線程處於阻塞狀態。CountDownLatch、CyclikBarrier、Semaphore 都有一個int類型參數的構造方法。
  • CountDownLatch、CyclikBarrier、Semaphore 都有一個int類型參數的構造方法。

CountDownLatch、CyclicBarrier和Semaphore使用場景

CountDownLatch

由於CountDownLatch有個countDown()方法並且countDown()不會引起阻塞,所以CountDownLatch可以應用於主線程等待所有子線程結束後再繼續執行的情況。具體使用方式爲new一個構造參數爲subThread數目的CountDownLatch,啓動所有子線程後主線程await(),在每個子線程的最後執行countDown(),這樣當所有子線程執行完後計數減爲0,主線程釋放等待繼續執行。比如賽跑,每個運動員看做一個子線程,裁判就是主線程,裁判發令(設置一個值爲1的計數器,發令之前所有子線程await等待命令,裁判員發令讓計數置爲0,所有子線程同時開跑)所有運動員開跑後,需要等待所有人跑完再統計成績(設置一個值爲運動員數目的計數器,所有運動員開跑後裁判await被阻塞,每個運動員跑完的時候countDown()一下,所有運動員跑完計數達到0,裁判釋放阻塞開始計分)。
  1. public class CountDownLatchCase {  
  2.       
  3.     public static void main(String args[]){  
  4.         //主線程爲裁判,子線程爲運動員  
  5.         final CountDownLatch operatorNum=new CountDownLatch(6);  
  6.         for(int i=0;i<6;i++){  
  7.             new Thread(new Runnable() {  
  8.                 @Override  
  9.                 public void run() {  
  10.                     Long s=System.currentTimeMillis();  
  11.                     Random random=new Random();  
  12.                     try {  
  13.                         Thread.sleep(random.nextInt(10000));  
  14.                     } catch (InterruptedException e) {  
  15.                         e.printStackTrace();  
  16.                     }  
  17.                     Long time=System.currentTimeMillis()-s;  
  18.                     System.out.println(String.format(”運動員%s跑完,花了%s”, Thread.currentThread().getName(),time));  
  19.                     //一個運動員跑完了  
  20.                     operatorNum.countDown();  
  21.                 }  
  22.             }).start();  
  23.         }  
  24.         try {  
  25.             operatorNum.await();  
  26.             System.out.println(String.format(”所有運行員都跑完了,裁判%s可宣佈結果啦!”, Thread.currentThread().getName()));  
  27.         } catch (InterruptedException e) {  
  28.             e.printStackTrace();  
  29.         }  
  30.           
  31.     }  
  32. }  
public class CountDownLatchCase {

    public static void main(String args[]){
        //主線程爲裁判,子線程爲運動員
        final CountDownLatch operatorNum=new CountDownLatch(6);
        for(int i=0;i<6;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Long s=System.currentTimeMillis();
                    Random random=new Random();
                    try {
                        Thread.sleep(random.nextInt(10000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Long time=System.currentTimeMillis()-s;
                    System.out.println(String.format("運動員%s跑完,花了%s", Thread.currentThread().getName(),time));
                    //一個運動員跑完了
                    operatorNum.countDown();
                }
            }).start();
        }
        try {
            operatorNum.await();
            System.out.println(String.format("所有運行員都跑完了,裁判%s可宣佈結果啦!", Thread.currentThread().getName()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

CyclicBarrier

由於CyclicBarrier計數達到指定後會重新循環使用,所以CyclicBarrier可以用在所有子線程之間互相等待多次的情形。比如在某種需求中,比如一個大型的任務,常常需要分配好多子任務去執行,只有當所有子任務都執行完成時候,才能執行主任務,這時候,就可以選擇CyclicBarrier了。 比如團隊旅遊,一個團隊通常分爲幾組,每組人走的路線可能不同,但都需要到達某一地點等待團隊其它成員到達後才能進行下一站。
  1. /**   
  2.  * 各省數據獨立,分庫存偖。爲了提高計算性能,統計時採用每個省開一個線程先計算單省結果,最後彙總。   
  3.  *    
  4.  * @author guangbo email:[email protected]   
  5.  *    
  6.  */    
  7. public class Total {     
  8.     
  9.     // private ConcurrentHashMap result = new ConcurrentHashMap();     
  10.     
  11.     public static void main(String[] args) {     
  12.         TotalService totalService = new TotalServiceImpl();     
  13.         CyclicBarrier barrier = new CyclicBarrier(5,     
  14.                 new TotalTask(totalService));     
  15.     
  16.         // 實際系統是查出所有省編碼code的列表,然後循環,每個code生成一個線程。     
  17.         new BillTask(new BillServiceImpl(), barrier, “北京”).start();     
  18.         new BillTask(new BillServiceImpl(), barrier, “上海”).start();     
  19.         new BillTask(new BillServiceImpl(), barrier, “廣西”).start();     
  20.         new BillTask(new BillServiceImpl(), barrier, “四川”).start();     
  21.         new BillTask(new BillServiceImpl(), barrier, “黑龍江”).start();     
  22.     
  23.     }     
  24. }     
  25.     
  26. /**   
  27.  * 主任務:彙總任務   
  28.  */    
  29. class TotalTask implements Runnable {     
  30.     private TotalService totalService;     
  31.     
  32.     TotalTask(TotalService totalService) {     
  33.         this.totalService = totalService;     
  34.     }     
  35.     
  36.     public void run() {     
  37.         // 讀取內存中各省的數據彙總,過程略。     
  38.         totalService.count();     
  39.         System.out.println(”=======================================”);     
  40.         System.out.println(”開始全國彙總”);     
  41.     }     
  42. }     
  43.     
  44. /**   
  45.  * 子任務:計費任務   
  46.  */    
  47. class BillTask extends Thread {     
  48.     // 計費服務     
  49.     private BillService billService;     
  50.     private CyclicBarrier barrier;     
  51.     // 代碼,按省代碼分類,各省數據庫獨立。     
  52.     private String code;     
  53.     
  54.     BillTask(BillService billService, CyclicBarrier barrier, String code) {     
  55.         this.billService = billService;     
  56.         this.barrier = barrier;     
  57.         this.code = code;     
  58.     }     
  59.     
  60.     public void run() {     
  61.         System.out.println(”開始計算–” + code + “省–數據!”);     
  62.         billService.bill(code);     
  63.         // 把bill方法結果存入內存,如ConcurrentHashMap,vector等,代碼略     
  64.         System.out.println(code + ”省已經計算完成,並通知彙總Service!”);     
  65.         try {     
  66.             // 通知barrier已經完成     
  67.             barrier.await();     
  68.         } catch (InterruptedException e) {     
  69.             e.printStackTrace();     
  70.         } catch (BrokenBarrierException e) {     
  71.             e.printStackTrace();     
  72.         }     
  73.     }     
  74.     
  75. }   
/**  
 * 各省數據獨立,分庫存偖。爲了提高計算性能,統計時採用每個省開一個線程先計算單省結果,最後彙總。  
 *   
 * @author guangbo email:[email protected]  
 *   
 */  
public class Total {   

    // private ConcurrentHashMap result = new ConcurrentHashMap();   

    public static void main(String[] args) {   
        TotalService totalService = new TotalServiceImpl();   
        CyclicBarrier barrier = new CyclicBarrier(5,   
                new TotalTask(totalService));   

        // 實際系統是查出所有省編碼code的列表,然後循環,每個code生成一個線程。   
        new BillTask(new BillServiceImpl(), barrier, "北京").start();   
        new BillTask(new BillServiceImpl(), barrier, "上海").start();   
        new BillTask(new BillServiceImpl(), barrier, "廣西").start();   
        new BillTask(new BillServiceImpl(), barrier, "四川").start();   
        new BillTask(new BillServiceImpl(), barrier, "黑龍江").start();   

    }   
}   

/**  
 * 主任務:彙總任務  
 */  
class TotalTask implements Runnable {   
    private TotalService totalService;   

    TotalTask(TotalService totalService) {   
        this.totalService = totalService;   
    }   

    public void run() {   
        // 讀取內存中各省的數據彙總,過程略。   
        totalService.count();   
        System.out.println("=======================================");   
        System.out.println("開始全國彙總");   
    }   
}   

/**  
 * 子任務:計費任務  
 */  
class BillTask extends Thread {   
    // 計費服務   
    private BillService billService;   
    private CyclicBarrier barrier;   
    // 代碼,按省代碼分類,各省數據庫獨立。   
    private String code;   

    BillTask(BillService billService, CyclicBarrier barrier, String code) {   
        this.billService = billService;   
        this.barrier = barrier;   
        this.code = code;   
    }   

    public void run() {   
        System.out.println("開始計算--" + code + "省--數據!");   
        billService.bill(code);   
        // 把bill方法結果存入內存,如ConcurrentHashMap,vector等,代碼略   
        System.out.println(code + "省已經計算完成,並通知彙總Service!");   
        try {   
            // 通知barrier已經完成   
            barrier.await();   
        } catch (InterruptedException e) {   
            e.printStackTrace();   
        } catch (BrokenBarrierException e) {   
            e.printStackTrace();   
        }   
    }   

} 



Semaphore

Semaphore可以用於做流量控制,特別公用資源有限的應用場景,比如數據庫連接。假如有一個需求,要讀取幾萬個文件的數據,因爲都是IO密集型任務,我們可以啓動幾十個線程併發的讀取,但是如果讀到內存後,還需要存儲到數據庫中,而數據庫的連接數只有10個,這時我們必須控制只有十個線程同時獲取數據庫連接保存數據,否則會報錯無法獲取數據庫連接。這個時候,我們就可以使用Semaphore來做流控,代碼如下:
  1. public class SemaphoreCase {  
  2.   
  3.     private static final int THREAD_COUNT = 30;  
  4.     private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);  
  5.     private static Semaphore s = new Semaphore(10);  
  6.     public static void main(String[] args) {  
  7.         for (int i = 0; i < THREAD_COUNT; i++) {  
  8.             threadPool.execute(new Runnable() {  
  9.                 @Override  
  10.                 public void run() {  
  11.                     try {  
  12.                         s.acquire();  
  13.                         System.out.println(”save data”);  
  14.                         s.release();  
  15.                     } catch (InterruptedException e) {  
  16.                     }  
  17.                 }  
  18.             });  
  19.         }  
  20.         threadPool.shutdown();  
  21.     }  
  22.   
  23. }  
public class SemaphoreCase {

    private static final int THREAD_COUNT = 30;
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
    private static Semaphore s = new Semaphore(10);
    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        s.acquire();
                        System.out.println("save data");
                        s.release();
                    } catch (InterruptedException e) {
                    }
                }
            });
        }
        threadPool.shutdown();
    }

}
注意:
  • release函數和acquire並沒有要求一定是同一個線程都調用,可以A線程申請資源,B線程釋放資源;
  • 調用release函數之前並沒有要求一定要先調用acquire函數。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章