热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

利用多线程实现报表的高效导出多线程、线程池、并发包每当谈起这些词汇,可能不是在面试就是在准备面试的路上了。有句话叫“面试造航母,工作拧螺丝“,确实很多情况下我们是用不到这些东西
多线程实现报表的高效导出

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

有句话叫“面试造航母,工作拧螺丝“,确实很多情况下我们是用不到这些东西的,但是学好这些东西对我们的日常工作也可能会产生意想不到的好处的。

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

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

多线程实现的基础知识

实现多线程的方式

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

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

  • newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。

  • newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。

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

  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

 如何自定义线程池

在实际的使用过程中,一般我们都是用Executors去创建线程池,如果有一些其他的需求,比如指定线程池的拒绝策略,阻塞队列的类型,线程名称的前缀等等,我们可以采用自定义线程池的方式来解决。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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> borrowIncrement = service.submit(new Callable>() {
            @Override
            public List call() throws Exception {

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

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

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

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

确实是存在这个情况的,其实我们可以利用Future对象的 cancel(boolean mayInterruptIfRunning)来中断其他线程,底层其实还是thread.interrupt()的方法实现。

总结

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

最后,新年临近,祝福大家新年快乐,也希望自己能够在新的一年做一个合格的creative worker。

posted on 2019-01-31 18:48  HackerVirus  阅读(2410)  评论(0)  编辑  收藏  举报

推荐阅读
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
author-avatar
菜蕻的薇笑2602929033
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有