引言
当我们在项目遇到一次 IO 密集型的操作,会进行大量的数据库查询,我们可以考虑使用多线程来解决这个问题。
背景
首先看下这个需求的具体流程,数据库中保存了所有的订单数据,现在老板让我统计某个时间段里每天的订单数量以及订单状态,由于早期设计问题,订单表中使用的是时间戳对时间进行存储,这导致了我们没有办法使用类似 group by
的方式进行直接分类,再加上也不想写一些复杂 sql
,所以准备以传入的起始时间为起点,遍历从这个时间点起每天的数据。
问题陈述
相信通过上面的描述,大家已经大概知道问题所在,那就是太耗时,如果时间段选的足够长的话,我们的遍历时间会非常的缓慢,其中最主要的操作当然就是 sql
查询,因为需要查询这个时间段里每天是否存在数据,并且进行汇总。
解决方案
因此,考虑到其中大量的都是 IO
操作,我们完全可以使用多线程的方式,将这个时间段分为有限多个时间段,然后每个线程分别对某个时间段进行数据查询,并且最终添加到我们的 list
当中。
结果
首先给大家看一下结果,效果还是非常明显。
优化前:
优化后:
代码
首先我们创建了一个任务类,这个任务类就是我们每个线程需要执行的任务。
class FitDailyDataTask implements Runnable {
// 我们查询出来的任务将会放入这个列表当中
private List<DailyDataResp> dailyDataRespList;
// 开始的时间
private long durationStart;
// 结束的时间
private long durationEnd;
// 我们在构建这个任务的时候,传入需要的参数
public FitDailyDataTask(List<DailyDataResp> dailyDataRespList, long durationStart, long durationEnd) {
this.dailyDataRespList = dailyDataRespList;
this.durationStart = durationStart;
this.durationEnd = durationEnd;
}
@Override
public void run() {
// 循环装填整个测试数据
long curDurationStart = durationStart;
// 结束时间
long curDurationEnd = 0;
while (curDurationStart <= durationEnd) {
curDurationEnd = curDurationStart + ONE_DAY;
// 以下就是我们进行的 SQL 操作,这里省略了部分代码,大家自己脑补
// 查询当前日期检查人数
// TODO SQL操作
// 数据查询出来之后插入我们的 list 中
DailyDataResp dailyDataResp = new DailyDataResp();
dailyDataRespList.add(dailyDataResp);
curDurationStart += ONE_DAY;
}
}
}
这里大家需要注意一点,我们的代码中用到的列表是 Vector
,这是为了保证在多线程插入的情况下线程安全问题。
接下来就是我们的核心代码了。
/**
* 装填列表数据
* @param dailyDataRespList 装填的列表
* @param durationStart 开始时间
* @param durationEnd 结束时间
*/
private void fitDailyDataRespList(List<DailyDataResp> dailyDataRespList,
long durationStart, long durationEnd) {
// 创建一个线程池,核心线程数根据我们的机器来定
ThreadPoolExecutor pool = new ThreadPoolExecutor(
CORE_SIZE,
CORE_SIZE,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(CORE_SIZE));
// 这里计算出我们每个线程需要处理的时间段
long step = (durationEnd - durationStart) / ONE_DAY / CORE_SIZE * ONE_DAY;
// 创建任务并且提交给我们的线程池执行
for (int i = 0; i <= CORE_SIZE; i++) {
long start = durationStart + (step * i);
long end = start + step - 1;
pool.submit(new FitDailyDataTask(dailyDataRespList, start, end, hospitalId));
}
// 线程池使用完毕之后需要关闭,避免内存泄露问题
pool.shutdown();
try {
// 这里我们需要等待线程池执行完以上的任务,因为后续我们可能对这个列表进行其他处理
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
结论
以上就是我这次在项目实战当中使用多线程的一次经历,虽然平时在项目中,大家总是说可能工作个三五年都用不到一次多线程,但是面试环节多线程永远是绕不开的话题,想必也是因为这个原因,所以大家一定要好好的掌握相关知识。