利用多线程实现报表的高效导出
多线程、线程池、并发包每当谈起这些词汇,可能不是在面试就是在准备面试的路上了。
有句话叫“面试造航母,工作拧螺丝“,确实很多情况下我们是用不到这些东西的,但是学好这些东西对我们的日常工作也可能会产生意想不到的好处的。
临近年末,收拾了下手头工作,趁着最后两天有些闲暇,准备着手优化下前段时间业务人员反馈的部分报表导出速度过慢的问题。
报表的优化主要是涉及两个方面,一个是SQL和数据库层面的优化,另一个就是代码层面的优化了,本文主要讲述代码层面利用多线程处理的一点小总结。
多线程实现的基础知识
实现多线程的方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 实现Callable接口创建线程
- 线程池的实现
JDK自带的五种线程池的使用场景
newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
如何自定义线程池
在实际的使用过程中,一般我们都是用Executors去创建线程池,如果有一些其他的需求,比如指定线程池的拒绝策略,阻塞队列的类型,线程名称的前缀等等,我们可以采用自定义线程池的方式来解决。
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>>() {@Overridepublic 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>>() {@Overridepublic 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>>() {@Overridepublic 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>>() {@Overridepublic 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。
转载于:https://www.cnblogs.com/laoyeye/p/10340551.html
利用多线程实现报表的高效导出相关推荐
- linux+Qt 下利用D-Bus进行进程间高效通信的三种方式
linux+Qt 下利用D-Bus进行进程间高效通信的三种方式 原文链接: https://www.cnblogs.com/wwang/archive/2010/10/27/1862552.html ...
- Asp.net高效导出excel篇之Aspose导出excel
上周在博客中写了一篇<Asp.net高效导出Excel篇>在发布之后收到很多热心网友的建议--使用Excel第三方引擎如NOPI.Aspose.cell等导出Excel,优点:效率高.不需 ...
- [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(三) 利用多线程提高程序性能(下)...
[.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(二) 利用多线程提高程序性能(下) 本节导读: 上节说了线程同步中使用线程锁和线程通知的方式来处理资源共享问题,这 ...
- java多线程实现归并排序_利用多线程对数组进行归并排序
多线程处理归并排序的方法一般为: 假设有n个线程同步处理,就将数组等分成n份,每个线程处理一份,再对最后n个有序数组进行归并. 为了使对整个算法具有可扩展性,即线程数n可以自定义,笔者将线程类.处理数 ...
- 利用多线程解决多业务不同定时区间歇触发问题的一种方法
利用多线程解决多业务不同定时区间歇触发问题的一种方法 参考文章: (1)利用多线程解决多业务不同定时区间歇触发问题的一种方法 (2)https://www.cnblogs.com/naaoveGIS/ ...
- java游戏输赢统计_java利用多线程和Socket实现猜拳游戏
本文实例为大家分享了利用多线程和Socket实现猜拳游戏的具体代码,供大家参考,具体内容如下 实例:猜拳游戏 猜拳游戏是指小时候玩的石头.剪刀.布的游戏.客户端与服务器的"较量", ...
- oracle 导出数据 utl,【原创】利用utl_file包进行表数据导出
利用utl_file包进行表数据导出 2013/11/12 一.概述 工作中需要将许多表的数据导出到一份excel中,虽然PL/SQL自带有导出的功能,但是导出后需要进行手动的汇总到一张excel中, ...
- python paramiko并发_使用Python paramiko模块利用多线程实现ssh并发执行操作
1.paramiko概述 ssh是一个协议,OpenSSH是其中一个开源实现,paramiko是Python的一个库,实现了SSHv2协议(底层使用cryptography). 有了Paramiko以 ...
- ICCV 2021 Oral | AdaFocus:利用空间冗余性实现高效视频识别
©原创 · 作者 | 王语霖 单位 | 清华大学自动化系 研究方向 | 机器学习.计算机视觉 本文主要介绍我们被 ICCV 2021 会议录用为 Oral Presentation 的一篇文章:Ada ...
最新文章
- 高并发系列:存储优化之也许可能是史上最详尽的分库分表文章之一
- 我马上会重新利用这个博客的
- 【Linux】开源分布式存储系统:GlusterFS
- 5 拦截器拦截请求路由_手写简易版axios拦截器,实现微信小程序wx.request的封装与拦截...
- Jquery操作CSS常用方法
- Centos7设置DNS开机启动
- django 1.8 官方文档翻译:13-6 分页
- 微软公布测试版Visual Studio for Mac和Visual Studio 2017 for Windows
- 二维码_encode与decode
- 实用的SQL函数(用于将符合条件的某列所有记录合成一行)
- wordpress评论审核通过发邮件给评论者
- FFT蝶形算法的verilog实现专题——FFT的matlab到verilog转化过程——第1部分
- Crosses and Crosses POJ - 3537 (博弈)
- ElasticSearch全文检索-从零到入门
- 介绍两个测试网页打开速度的网站
- html文本框自动宽度,input文本框宽度自适应
- 传国宝玺 第二部 降墓 第十六章 三煞天棺
- js购物车功能php,使用JS实现购物车功能步骤详解
- 密码学归约证明——定长对称加密密钥的敌手不可区分性
- 如何快速转行产品经理 少走弯路
热门文章
- Tensorflow实战之下载MNIST数据,自动分成train, validation和test三个数据集
- 关于数据集 使用PutCollect后m_Recordset-Update();数据集更新报错的问题
- 在mysql中创建表的命令行_如何在命令行创建一个MySQL数据库
- token、cookie是什么
- mysql 查询姓张或者姓王_mysql查询练习
- 变量置换方式linux,Shell变量测试与内容置换
- c语言scarf函数原型,scanf 函数原型.doc
- Tomcat的Classpath-常见问题以及如何解决
- 逻辑运算符和||与(和|)的区别
- 055_Descriptions描述列表