一次CountDownLatch的實際運用

業務

最近在公司負責關於大風車(公司旗下一款SaaS產品)的組織權限的重構,遇到一點問題:初始化組織架構樹時間比較慢(2s-3s),這篇文章就是介紹使用CountDownLatch解決這個問題。

在重構之前組織架構的樹是基於人員組成的,也就是一棵單純人員樹,但是由於人員樹的缺陷(當主管或其他高級職位的人員離職,那麼下屬就沒有節點可以掛靠),所以我重構的時候放棄以人員爲緯度,採用區域樹,人員掛靠是以區域爲緯度,這樣任何人離職,都不會影響組織架構的完整性。

問題

之前採用人員樹的時候,由於很多城市是沒有人的,所以查詢速度挺快,但是現在以區域爲緯度,那麼每一個城市我們都要去查詢到,然後關聯查詢將人員掛上去,由於區域層級較深,全國-大區->區域->城市->區(一對多的關係)。所以查詢起來效率比較慢(2s-3s)。看到這個問題,我所知道的解決方案有兩種,一種是找DBA優化SQL,一種是多線程去跑。

本着儘量不要麻煩別人的想法,我決定採用多線程去完成這棵組織架構樹。因爲涉及到同步一批線程的行爲,所以要在併發包中CountDownLatch、Semaphore和CyclicBarrier選一個解決問題。最終選擇了CountDownLatch。

實現過程

我們先看定義的實體類:


public class ReginDo {
    //區域
    private int id;
    private String areaName;
    private Integer areaLevel;

    //這裏應該是List<User>,爲了演示的簡單,這裏沒有用
    private String userID;
    private String userName;
    private int userLevel;

    //下層區域
    private List<ReginDo> sons;
    
}

定義不難理解,就不細說了,來看具體的實現

    public static void main(String[] args) throws InterruptedException {
        //模擬從數據庫取出全國4個大區
        List<ReginDo> list = new ArrayList<>();

        ReginDo reginDo = new ReginDo();
        reginDo.setId(1);
        ReginDo reginDo1 = new ReginDo();
        reginDo1.setId(2);
        ReginDo reginDo2 = new ReginDo();
        reginDo2.setId(3);
        ReginDo reginDo3 = new ReginDo();
        reginDo3.setId(4);

        list.add(reginDo);
        list.add(reginDo1);
        list.add(reginDo2);
        list.add(reginDo3);

        //模擬數據庫獲取數據結束
        
        //定義線程數
         CountDownLatch count = new CountDownLatch(list.size());
         
        //計時
        long st = System.currentTimeMillis();
        
        //模擬從數據庫獲取大區下的其他層級
         for (int i = 0; i < list.size(); i++) {
            int finalI = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
                ReginDo reginDo4 = list.get(finalI);
                List<ReginDo> sons = new ArrayList<>();
                ReginDo reginDo5 = new ReginDo();
                reginDo5.setId(finalI + 10);
                sons.add(reginDo5);
                reginDo4.setList(sons);
                try {
                //睡眠1s,模擬效果,實際肯定用不到1s.
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                 count.countDown();
            }).start();
        }
        count.await();
        for (ReginDo reginDo4 : list) {
            System.out.println(reginDo4);
        }
        System.out.println("全部執行完畢");
        
        //基本上都是1100左右
        System.out.println((System.currentTimeMillis() - st));
        
        //下面是不適用CountDownLatch的邏輯
        
        long st2 = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            System.out.println(Thread.currentThread().getName());
            ReginDo reginDo4 = list.get(i);
            List<ReginDo> sons = new ArrayList<>();
            ReginDo reginDo5 = new ReginDo();
            reginDo5.setId(i + 10);
            sons.add(reginDo5);
            reginDo4.setList(sons);
            Thread.sleep(1000);
        }

        for (ReginDo reginDo4 : list) {
            System.out.println(reginDo4);
        }
        System.out.println("全部執行完畢");
        //輸出在4000左右
        System.out.println((System.currentTimeMillis() - st2));
    }
    

實際效果查詢效果已經成爲毫秒級,就是拆分邏輯,用多線程去跑,使用CountDownLatch在業務上保證同步。

當然你也可以使用FutureTask的get達到阻塞效果。

關於CountDownLatch我在慕課網上看到這樣一個比喻:

CountDownLatch就像大型商場裏面的臨時遊樂場,每一場遊樂的時間過後等待的人同時進場玩,而一場中間會有不愛玩了的人隨時出來,但不能進入,一旦所有進入的人都出來了,新一批人就可以同時進場

更多Java相關文章和Java面試題請移步小程序。
在這裏插入圖片描述

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