背景
最近在做的项目中有一个任务工作台功能,需要展示当前报表填报任务的总体进度,便于管理人员进行进度跟踪和整体感知。但是这个功能写完之后,当系统下发的报表任务较多时,任务工作台加载的就会很慢,大约5秒多才能完成加载。用户体验太差,正好这段时间在学习多线程相关的知识,就决定来优化一下。
改造过程
1、分析任务属于CPU密集型还是IO密集型
分清是计算(CPU)密集型任务还是IO密集型任务至关重要,直接关系到线程池的初始化参数的设置。
- 对于计算密集型的任务
在拥有N个处理器的系统上,当线程池的大小为N+1时,通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。)
- 对于IO密集型任务
事实上大部分的任务都是I/O密集型的,即大部分任务消耗集中在的输入输出(数据库数据交互、文件上传下载、网络数据传输等等)。线程中的任务最终是交给CPU的线程去处理的,而CPU可同时处理线程数量大部分是CPU核数的两倍,所以将线程池的核心池线程数量配置为CPU核数的两倍是比较合适的。
项目中的任务属于IO密集型,因为都是数据库操作,执行查询语句 。
运行环境中CPU的核数我们可以通过Runtime.getRuntime().availableProcessors()这个方来而获取。理论上来说核心池线程数量应该为Runtime.getRuntime().availableProcessors()*2
- 确定线程池的核心池大小和最大值
// 获取CPU核心数
final int PROCESSORS = Runtime.getRuntime().availableProcessors();
// 判断当前要处理的任务数是否小于核心数
int corePoolSize = distList.size() < PROCESSORS ? distList.size():PROCESSORS;
ExecutorService pool = new ThreadPoolExecutor(corePoolSize*2, PROCESSORS*8,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
2、对需要共享的变量使用Atomic替换
AtomicInteger finishedNum = new AtomicInteger(0);
AtomicInteger unfinishedNum = new AtomicInteger(0);
AtomicInteger notStartedNum = new AtomicInteger(0);
3、对结果集进行线程安全包装
List<Map<String,Object>> progressList = Collections.synchronizedList(new ArrayList<Map<String,Object>>());
最终结果
1、最终代码
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public Map<String,Object> getRepProgress(){
final int PROCESSORS = Runtime.getRuntime().availableProcessors();
Map<String,Object> tjMap = new HashMap<String,Object>(16);
List<Map<String,Object>> progressList = Collections.synchronizedList(new ArrayList<Map<String,Object>>());
List<Map<String,Object>> distList = reportDao.getDistRecordList();
AtomicInteger finishedNum = new AtomicInteger(0);
AtomicInteger unfinishedNum = new AtomicInteger(0);
AtomicInteger notStartedNum = new AtomicInteger(0);
CountDownLatch latch = new CountDownLatch(distList.size());
int corePoolSize = distList.size() < PROCESSORS ? distList.size():PROCESSORS;
ExecutorService pool = new ThreadPoolExecutor(corePoolSize*2, PROCESSORS*8,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (Map<String,Object> map:distList) {
pool.execute(() -> {
// ....业务代码
// 填报单位总数
long sum = 0;
// 已上报单位数
long ysbSl = 0;
String status = "";
if(ysbSl == 0){
// 未开始
status = "01";
notStartedNum.incrementAndGet();
}else if(ysbSl == sum){
// 已完成
status = "03";
finishedNum.incrementAndGet();
}else{
// 进行中
status = "02";
unfinishedNum.incrementAndGet();
}
Map<String,Object> progressMap = new HashMap<String,Object>(16);
progressMap.put("status",status);
progressMap.put("sbqk",ysbSl+"/"+sum);
progressMap.put("progress",Math.round((float) ysbSl/(float)sum*100));
progressList.add(progressMap);
latch.countDown();
});
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 关闭线程池
pool.shutdown();
System.out.println("计算任务处理完毕");
tjMap.put("reportNum",distList.size());
tjMap.put("unfinishedNum",unfinishedNum);
tjMap.put("finishedNum",finishedNum);
tjMap.put("notStartedNum",notStartedNum);
tjMap.put("progressList",progressList);
return tjMap;
}
2、优化结果
- 代码未优化前
执行时间为5408ms,平均下来也有50xxms
- 代码优化后,引入多线程进行处理后
在4核处理器上运行,大概在1325ms上下
在8核处理器上运行,大约900ms上下