记一次性能优化:通过 CountDownLatch闭锁 来解决后台导出文件服务,线程超时,异常处理

283 阅读3分钟

问题背景

目前业务有一个需求, 客户想要导出近3年的各模块数据汇总成xlxs表格,数据库数据达1亿+;

以及面试过程经常问到的,了解Java并发包吗,有CountDownLatch闭锁真实业务使用过吗,如何吹牛b

问题难点

 很多同学会想到,异步导出,即前端服务请求后台,后台检验基本参数后,
 直接返回前端,提示‘正在后台导出任务,请稍后至文件下载中心查看文件’,然后开启后台异步线程去做 数据收集,汇总,生成xlxs

image.png

但是这里依旧有一个问题,刚才提到过,后台数据库数据达1亿,并且这张xlxs的表格数据,可能来源于查询A模   
块,B模块,C模块等.....,假设A模块处理耗时30s,B模块耗时60s,C模块耗时90s,那么会导致后台线程的运行时间很长很长,

也就是后台看似是异步线程,实际逻辑走的是同步查询,从而导致整个后台线程耗时很长,
从而 *********** 错失年终,拿到大礼包,如图楼主个人未优化之前的后台耗时达15s

image.png

解决思路CountDownLatch闭锁

我这里的主要想法是 后台既然真实逻辑是按同步走的,那我能不能将后台的 ‘同步’ 搞成 异步来处理,如图

image.png

就好像:打LOL的时候,点击开始游戏(后台开启),之后进入等待页面,正在等待玩家一,等待玩家二...等待玩家三(异步处理),最后所有玩家都准备好后,然后游戏正式开始(主线程运行)
这里写一段伪代码,可以copy下来去感受下
public class CountDownLatchCompare {

    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < countDownLatch.getCount(); i++) {
            new Thread(new MyThread(countDownLatch), "玩家" + i).start();
        }
        System.out.println("正在等待所有玩家准备好");
        countDownLatch.await();
        System.out.println("开始游戏 go go go");
    }

    private static class MyThread implements Runnable {
        private CountDownLatch countDownLatch;

        private MyThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                Random rand = new Random();
                int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;//产生1000到3000之间的随机整数
                Thread.sleep(randomNum);
                System.out.println(Thread.currentThread().getName() + " 已经准备好了, 所使用的时间为 " + ((double) randomNum / 1000) + "s");
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

image.png

因此对于刚才提到的问题,这里完全可以用同样的思想用CountDownLatch闭锁去处理,即
后台线程访问A,B,C不同服务采集表格数据时,采用CountDownLatch和ExecutorService进行异步处理,
  
即用ExecutorService开启对应的A,B,C等数据的后台采集
同时用CountDownLatch闭锁去监控A,B,C服务是否完成,当A,B,C都完成时,再进行数据装填进xlxs
ExecutorService executorService = Executors.newFixedThreadPool(3);
final CountDownLatch countDownLatch = new CountDownLatch(3);
    executorService.submit(new Runnable() {
    @Override
    public void run () {
        //todo A服务采集数据
        countDownLatch.countDown();
    }
});
    executorService.submit(new Runnable() {
    @Override
    public void run () {
        //todo B服务采集数据
        countDownLatch.countDown();
    }
});
 executorService.submit(new Runnable() {
    @Override
    public void run () {
        //todo C服务采集数据
        countDownLatch.countDown();
    }
});
    countDownLatch.await();
    executorService.shutdown();

效果展示:优化了三分之一时间

image.png

备注

后续再梳理一下concurrent并发包下的CyclicBarrier具体业务场景