業務
最近在公司負責關於大風車(公司旗下一款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面試題請移步小程序。