一次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,为了演示的简单,这里没有用
    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面试题请移步小程序。

你可能感兴趣的:(多线程)