利用多线程实现报表的高效导出

多线程、线程池、并发包每当谈起这些词汇,可能不是在面试就是在准备面试的路上了。面试

有句话叫“面试造航母,工做拧螺丝“,确实不少状况下咱们是用不到这些东西的,可是学好这些东西对咱们的平常工做也可能会产生意想不到的好处的。sql

临近年底,收拾了下手头工做,趁着最后两天有些闲暇,准备着手优化下前段时间业务人员反馈的部分报表导出速度过慢的问题。数据库

报表的优化主要是涉及两个方面,一个是SQL和数据库层面的优化,另外一个就是代码层面的优化了,本文主要讲述代码层面利用多线程处理的一点小总结。api

多线程实现的基础知识

实现多线程的方式

  • 继承Thread类建立线程
  • 实现Runnable接口建立线程
  • 实现Callable接口建立线程
  • 线程池的实现

JDK自带的五种线程池的使用场景

  • newSingleThreadExecutor:一个单线程的线程池,能够用于须要保证顺序执行的场景,而且只有一个线程在执行。多线程

  • newFixedThreadPool:一个固定大小的线程池,能够用于已知并发压力的状况下,对线程数作限制。并发

  • newCachedThreadPool:一个能够无限扩大的线程池,比较适合处理执行时间比较小的任务。ide

  • newScheduledThreadPool:能够延时启动,定时启动的线程池,适用于须要多个后台线程执行周期任务的场景。学习

  • newWorkStealingPool:一个拥有多个任务队列的线程池,能够减小链接数,建立当前可用cpu数量的线程来并行执行。优化

 如何自定义线程池

在实际的使用过程当中,通常咱们都是用Executors去建立线程池,若是有一些其余的需求,好比指定线程池的拒绝策略,阻塞队列的类型,线程名称的前缀等等,咱们能够采用自定义线程池的方式来解决。spa

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) ;
  • corePoolSize:线程池大小,决定着新提交的任务是新开线程去执行仍是放到任务队列中,也是线程池的最最核心的参数。通常线程池开始时是没有线程的,只有当任务来了而且线程数量小于corePoolSize才会建立线程。

  • maximumPoolSize:最大线程数,线程池能建立的最大线程数量。

  • keepAliveTime:在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。

  • unit:时间单位

  • workQueue:存放来不及处理的任务的队列,是一个BlockingQueue。

  • threadFactory:生产线程的工厂类,能够定义线程名,优先级等。

  • handler:拒绝策略,当任务来不及处理的时候,如何处理, 前面有讲解。

execute和submit的区别

  • execute适用于不须要关注返回值的场景,只须要将线程丢到线程池中去执行就能够了
  • submit方法适用于须要关注返回值的场景,在线程执行结束会返回响应的结果值

其实这两种方法的底层就是Runnable,Callable的实现。

多线程的一些基础小知识,有兴趣的同窗能够园子里翻翻其余同窗的介绍,多线程、线程池、并发包这些东西不管是学习仍是面试都是比较重要的。

报表优化案例

报表导出慢的缘由探查

仔细检查了须要优化的报表,发现由于这个报表的实时性要求比较高,同时涉及大量数据的计算操做,在优化了sql后效率仍是没法达到满意的程度,因此决定采用多线程的方式多个线程同时处理不一样的业务逻辑,最后在合并数据返回,以达到提升效率的目的。

代码解决方案

初步决定采用ExecutorService的submit方法,将一个复杂报表拆分为四个子线程执行并返回结果。同时采用并发包中的CountDownLatch作同步器,等待 四个子线程执行完毕后,再在主线程进行数据合并操做。假如每一个子线程的执行时长在10分钟左右,若是采用原先的串行方式的话,四个业务处理大概须要40分钟左右,如今这种并行的方式执行只须要十分钟的处理时间。

伪代码实现

        long startTime = DateUtils.getCurrentDateTime().getTime();
        ExecutorService service = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(4);
        Future<List<CapitalVO>> borrowIncrement = service.submit(new Callable<List<CapitalVO>>() {
            @Override
            public List<CapitalVO> call() throws Exception {

                List<CapitalVO> list = listBorrowIncrement(startDate, endDate);
                latch.countDown();
                return list;
            }
        });
        Future<List<OwnVO>> beceiveAccount = service.submit(new Callable<List<OwnVO>>() {
            @Override
            public List<OwnVO> call() throws Exception {

                List<OwnVO> list = listReceiveAccount(startDate, endDate);
                latch.countDown();
                return list;
            }
        });
        Future<List<OwnVO>> buaranteeAccount = service.submit(new Callable<List<OwnVO>>() {
            @Override
            public List<OwnVO> call() throws Exception {
                List<OwnVO> list = listGuaranteeAccount(startDate, endDate);
                latch.countDown();
                return list;
            }
        });
        Future<List<BorrowerVO>> borrowerRepayment = service.submit(new Callable<List<BorrowerVO>>() {
            @Override
            public List<BorrowerVO> call() throws Exception {
                List<BorrowerVO> list = listBorrowerRepayment(startDate, endDate);
                latch.countDown();
                return list;
            }
        });
            latch.await();
            List<CapitalVO> borrowCapitalIncrement = borrowIncrement.get();
            List<OwnVO> ownReceive = beceiveAccount.get();
            List<OwnVO> ownAccountGuan = buaranteeAccount.get();
            List<BorrowerVO> borrower = borrowerRepayment.get();

上述代码利用CountDownLatch实现了线程同步,同时解决了本来串行执行时间较长的问题,在最终的效果上也是达到了预期的优化目标,比原报表的处理时长减小了四分之三的时间。

另外,有同窗提出如今是实现了四个线程并行处理,处理时长大概在十分钟左右。可是假如其中一个线程出现了报错,不在须要其余线程继续执行,这个时候该怎么处理呢?

确实是存在这个状况的,其实咱们能够利用Future对象的 cancel(boolean mayInterruptIfRunning)来中断其余线程,底层其实仍是thread.interrupt()的方法实现。

总结

总的来讲技术方案上并无什么特别的东西,可是有时候有没有往这方面作就是一个思考的问题了。其实在工做中九成以上的人天天都是在作CRUD的业务,可是即使是CRUD每一个人作出来的东西仍是有所不一样的。多思考多实践,其实多线程并无那么高不可攀,即使是简单的报表,也是能够作出不同的东西的。

最后,新年临近,祝福你们新年快乐,也但愿本身可以在新的一年作一个合格的creative worker。

相关文章
相关标签/搜索