1. 程式人生 > >同步工具類 CountDownLatch 和 CyclicBarrier

同步工具類 CountDownLatch 和 CyclicBarrier

在開發中,一些非同步操作會明顯加快執行速度帶來更好的體驗,但同時也增加了開發的複雜度,想了用好多執行緒,就必須從這些方面去了解

  • 執行緒的 wait() notify() notifyall() 方法
  • 執行緒非同步返回 Future

  • ThreadLocal
  • 執行緒池 ThreadPoolExecutor
  • 同步工具類 CountDownLatch,CyclicBarrier,Semaphore,Phaser,Exchanger

估計上面每一個對於 2~3 年的 java 同學來說都是惡夢,比較難以理解,本文簡單說下 CountDownLatchCyclicBarrier

CountDownLatch

CountDownLatch 一般在執行時間較長的可分解的任務中比較常用,算是同步工具類中最容易理解的一個。

示例一:

一個簡單的例子:有一個 100 萬的 excel 資料匯出,需要從資料庫中查出資料,並封裝成 excel 資料然後輸出到前端。

稍微分析可以知道這個操作肯定會很費時間,它的瓶頸出在查詢資料庫資料和寫 excel 上,如果我每 10 萬資料一頁讀資料庫並寫 excel 成一個檔案,最後把所有的 excel 使用 zip 打包,使用多執行緒,由於讀資料庫並不會加鎖,效能將會有一個量級的提升(有實踐過),這時會有一個問題,我啟動多個執行緒後,主執行緒如何才能知道所有的執行緒都完成了呢,只有在所有執行緒都完成了後,才能對所有 excel 檔案進行打包,這時可以用 CountDownLatch

,虛擬碼如下:

// Excel 執行緒
class ExcelThread extend Thread{
    private CountDownLatch countDownLatch;
    public ExcelThread(CountDownLatch countDownLatch){
        this.countDownLatch = countDownLatch;
    }
    public void run(){
        try{
            // do query db & write excel 
        }finally{
            countDownLatch.countDown();
        }
    }
}

public static void main(){
    //假定生成 3 個 excel 
    CountDownLatch countdownlatch = new CountDownLatch(3);
    foreach : 
    new ExcelThread(countdownlatch).start();
    
    countDownLatch.await();
    
    // do zip compress & down 
}

示例二:

如果用過 IDM 下載工具,看它的下載進度一定知道它是多執行緒下載的,自動分成了多段,其實用 java 的 RandomAccessFile 配合 CountDownlatch 一樣可以做到多執行緒下載,可以例項化多個 RandomAccessFile 然後讓其指向檔案不同的檔案位置,然後向裡面填充資料即可,主執行緒在分執行緒全部完成後檢驗檔案完整性。

當然 IDM 做得更好,它會其它執行緒都完成了,如果某一段還卡著的話,繼續分隔,同樣多執行緒下載,當然速度就快了。

示例三:

對於CountDownLatch,其他執行緒為遊戲玩家,比如王者榮耀,主執行緒為控制遊戲開始的執行緒。在所有的玩家都準備好之前,主執行緒是處於等待狀態的,也就是遊戲不能開始。當所有的玩家準備好之後,下一步的動作實施者為主執行緒,即開始遊戲。

CyclicBarrier

相比如 CountDownLatch 是在主執行緒等待,CyclicBarrier 是子執行緒相互等待,而主執行緒早就已經結束了,並且在子執行緒相互等待的同時,可以附帶一個 CountDownLatch 類似功能的執行緒,等所有子執行緒都完成了再操作,並且 CyclicBarrier 是可重用的,說這麼多,看它的使用方式就知道什麼意思了。

五人六足之類的遊戲不知道讀者玩過沒,每個玩家是一個執行緒,必須互相等待對方準備好才可以進行下一步操作,其中也可以來一位指揮員他等到所有同學完成準備後下發指令,左腳,右腳。。。虛擬碼如下:

// 遊戲玩家執行緒
class GamePerson extend Thread{
    private CyclicBarrier cyclicBarrier;
    public GamePerson(CyclicBarrier cyclicBarrier){
        this.cyclicBarrier = cyclicBarrier;
    }
    public void run(){
        // 走下一步的準備階段
        prepareNextStep();
        cyclicBarrier.await(); 
        // 走下一步
        nextStep();
    }
}

// 主執行緒
public static void main(){
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    //構建  5 個遊戲玩家
    foreach:
    new GamePerson(cyclicBarrier);
    
    // 遊戲開始,並開始計時
    startGame();beginCountTime();
    
    foreach:gamepersons
        gameperson.start();
}

// 主執行緒,新增指揮員
class Leader extend Thread{
    public void run(){
        System.out.println(“開始走”);
    }
}
// 主執行緒
public static void main(){
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new Leader());
    //構建  5 個遊戲玩家
    foreach:
    new GamePerson(cyclicBarrier);
    
    // 遊戲開始,並開始計時
    startGame();beginCountTime();
    
    foreach:gamepersons
        gameperson.start();
}

// 前面的例子只能跨一步,如果需要重用 CyclicBarrier 需要把主執行緒也當做同步物件,程式碼如下 
public static void main(){
    CyclicBarrier cyclicBarrier = new CyclicBarrier(6);
    //構建  5 個遊戲玩家
    foreach:
    new GamePerson(cyclicBarrier);
    
    // 遊戲開始,並開始計時
    startGame();beginCountTime();
   
    //假設讓它們走 10 步
    for(int i=0;i<10;i++){
        cyclicBarrier.reset();
     foreach:gamepersons
        gameperson.start();
     cyclicBarrier.await();
   }
}

一點小推廣

創作不易,希望可以支援下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用匯入匯出,支援 Excel 公式
部落格地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板程式碼 ,從資料庫生成程式碼 ,及一些專案中經常可以用到的小工具
部落格地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-ma