记第一次使用线程池管理多线程

业务场景:首先系统会采集客户系统的数据库结构,入到知识库中。在此前提下,我需要将采集上来的数据查询出来,以excel文件的形式导出,此文件有12个sheet页,如下图:
一共12个sheet页

,保存系统数据库的所有详细数据,除基本信息之外还有属性信息,以数据量最大的表字段级信息为例,如下图:这里写图片描述

红色表头是基本信息,黑色表头是属性信息,某生产系统中有1405张表,37613个字段(注意,这些数据都是保存在知识库中,需要查询出来再写入文件),以字段为例,每条数据都要写明属于那个表、哪个库,为了保证此种关系的正确性,只能多层循环实现(可能有别的方法,小弟不才)。
在最初实现此功能时,导出这个系统的数据需要15分钟以上,性能极差。主要原因在于 单线程导出,既导出一个sheet页完毕再导第二个,以此类推导出12个sheet,每个单元格的数据都是从知识库中查询写入,读与写都非常耗时间。
在此背景下,确定了两种优化的方向:多线程与sql合并优化。此文只记录线程池管理多线程
第一步,创建无界线程池:
/**
* 选择使用无界线程池进行线程管理的原因为:
* 1、60s没再使用的线程,回收;
* 2、该线程池会复用空闲的线程,减少创建对象和回收对象带来开销;
* @author Gjd
*/
ExecutorService executor = Executors.newCachedThreadPool();
选择此线程池的原因是,假设12个sheet页中,只有表字段级信息耗时比较大,需要60s以上,那么剩下的11个线程在等待60s后会自动回收,节省了回收线程的时间(虽然只是毫秒级时间)。
第二步,创建线程:
/**
*

Description:导出线程,根据sheetCode的值,执行对应的导出方法;


* @date 2017年9月5日
*/
class exportThread extends Thread{
private List schemaList;
private HSSFSheet sheet;
private List mdifList;
private String sheetCode;
public exportThread(List schemaList,HSSFSheet sheet,List mdifList, String sheetCode) {
this.schemaList=schemaList;
this.sheet = sheet;
this.mdifList = mdifList;
this.sheetCode = sheetCode;
}
public void run() {
switch(sheetCode){
case “Schema”:
exportMethod.SchemaSheet(schemaList, sheet);
break;
case “Table” :
exportMethod.TableSheet(schemaList, sheet, mdifList);
break;
case “View” :
exportMethod.ViewSheet(schemaList, sheet, mdifList);
break;
case “Procedure” :
exportMethod.ProcedureSheet(schemaList, sheet, mdifList);
break;
case “TableColumn” :
exportMethod.TableColumnSheet(schemaList, sheet, mdifList);
break;
case “PrimaryKey” :
exportMethod.PrimaryKeySheet(schemaList, sheet, mdifList);
break;
case “ForeignKey” :
exportMethod.ForeignKeySheet(schemaList, sheet, mdifList);
break;
case “SQLIndex” :
exportMethod.SQLIndexSheet(schemaList, sheet, mdifList);
break;
case “ViewColumn” :
exportMethod.ViewColumnSheet(schemaList, sheet, mdifList);
break;
case “ProcedureColumn” :
exportMethod.ProcedureColumnSheet(schemaList, sheet, mdifList);
break;
case “ProcedureTable” :
exportMethod.ProcedureTableSheet(schemaList, sheet, mdifList);
break;
case “TdMacro” :
exportMethod.TdMacroSheet(schemaList, sheet, mdifList);
break;
default:
break;
}
}
}
线程的作用是根据传过来的sheetCode内容,调用不同的导出方法。

第三步,在下面的for循环中,创建线程并放入线程池中,具体的导出操作交给线程调用的实现方法,线程池只负责检测多个线程的工作状态。
for (int i = 0; i < ccnfList.size(); i++) {
sheet = (HSSFSheet) wbs.getSheet(this.loadstcnById(ccnfList.get(i).getWkshet_info_id()).getTmpt_wkshet_nm());// 创建sheet对象
mdifList = this.loadmdiflist(ccnfList.get(i).getId(),ccnfList.get(i).getClasf_cd());// 获取到属性与当前sheet页列位置的对应关系
schemaList = dataMapService.queryChildrenInstList(inst.getId());// 系统下的所有库
//创建好线程交给线程池管理,节约cpu资源
if (“Column”.equals(ccnfList.get(i).getClasf_cd()) && “Table”.equals(ccnfList.get(i).getPar_clasf_cd())) {// 表字段级
ccnfList.get(i).setClasf_cd(“TableColumn”);
}else if (“Column”.equals(ccnfList.get(i).getClasf_cd()) && “View”.equals(ccnfList.get(i).getPar_clasf_cd())) {// 视图字段级
ccnfList.get(i).setClasf_cd(“ViewColumn”);
}
exportThread export = new exportThread(schemaList, sheet, mdifList,ccnfList.get(i).getClasf_cd());
executor.execute(export);//放入线程池中
}

第四步,关闭线程池。
/**
* 作用:关闭线程池,线程方法2中线程池不再接收新的线程
* @author Gjd
*/
executor.shutdown();

第五步,等待线程执行完毕。
/**
* 作用:等待线程池中的所有线程做完
* 解释:判断条件为真(还有正在执行任务的线程),则执行循环体的代码(无代码是因为没有任何的业务操作了),条件为假(所有线程关闭)则继续执行循环体后面的代码
* @author Gjd
*/
while(!executor.isTerminated())
{}

第六步,进行下载(导出)的动作。
/**
* 所有线程执行完毕,开始进行导出的动作
* @author Gjd
*/
response.reset();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyyMMddhhmmss”);
String date = sdf.format(new Date());
String fileName = inst.getInst_nm() + “-” + date + “-” + file.getFile_nm();
// IE浏览器导出的文件名乱码,使用此方法处理编码
fileName = java.net.URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
response.setHeader(“Content-Disposition”, “attachment; filename=” + fileName);
response.setContentType(“application/octet-stream; charset=ISO-8859-1”);
OutputStream out = response.getOutputStream();
wbs.write(out);
out.close();

总结一下对于多线程和线程池的理解。
在工作量一定的情况下(假如12个任务),线程就是生活中的员工,负责干具体的活儿,而线程池就像生活中的领导(管理员),不关心你具体在做什么,只关心是否做完。
单线程就像雇了一个员工干完了所有的活儿,效率肯定慢;
无线程池管理的多线程就像雇了12个员工同事干活(此时其实效率提高了),干完自己的分内之事就歇了,无论工作量多少,公司都(CPU)要支付12个员工的钱,消耗较大。
使用线程池管理的多线程,可能公司(CPU)只雇了6个人,工作量少的员工(先执行完毕的线程),继续做下一件事儿,做完这件还有下件……直到最后一个员工完成工作,所有的工作就算完成,同样的效率,公司(CPU)不用雇12个人不用给12个人薪水,自然节省了资源的开销。
如此,提高了代码的效率,减小了资源的浪费。导出同样的37613条数据,可以控制在15s左右,与优化之前相比提高了60倍(一部分的功劳是多线程,另一部分是sql的优化)。

猜你喜欢

转载自blog.csdn.net/v_axis/article/details/77980586
今日推荐