前言
在开发过程中可能会碰到某些独特的业务,比如查询全部表数据,数据量过多会导致查询变得十分缓慢。虽然在大多数情况下并不需要查询所有的数据,而是通过分页或缓存的形式去减少或者避免这个问题,但是仍然存在需要这样的场景,比如需要导出所有的数据到excel中,导出数据之前,肯定需要先查询表中数据,这个查询的过程中数据量一旦过大,单线程查询数据会严重影响程序性能,有可能过长的查询时间导致服务宕机。现在模拟使用多线程来查询一张数据量较大的表。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是线程,什么是线程池
这一块内容就不再做过多的赘述,本文章只是直接使用案例来使用它们。不太了解的可自行百度查一下。
二、使用步骤
- 查询出表的数据总量。
- 数据的切分,根据本机CPU的核数配置合适数量的线程处理数,根据数据总量为不同的线程分配不同的查询数据量分段,即不同的线程查询不同分段的数据。
- 将各个查询数据的线程提交至线程池,这里使用的线程是带有返回结果的异步线程。(这样能把所有查询结果合并,才能进行下一步的操作。拿不到处理后的数据还怎么做下一步操作呢,所以一点要选择带有返回结果的线程)
controller层:
@GetMapping("/getAllData")
@ApiOperation(value = "多线程获取日志数据",)
public class MultiThreadController {
@Autowired
private ThredService thredService;
@GetMapping("/testThredData")
public List testThredData(){
return thredService.getAllResult();
}
}
service层:
public interface ThredService {
public List getAllResult();
}
serviceImpl层:
@Service
public class ThredServiceImpl implements ThredService {
@Autowired
private MultiThreadQueryUtil multiThreadQueryUtil;
@Override
public List getMultiCombineResult() {
return multiThreadQueryUtil.getMultiCombineResult();
}
}
多线程实现类(核心)
@Service
public class MultiThreadQueryUtil {
@Autowired
private WorkflowTaskMapper workflowTaskMapper;
/**
* 获取多线程结果并进行结果合并
* @return
*/
public List<List> getMultiCombineResult() {
//开始时间
long start = System.currentTimeMillis();
//返回结果
List<List> result = new ArrayList<>();
//查询数据库总数量
int count = workflowTaskMapper.selectCountAll();
Map<String,String> splitMap = ExcelLocalUtils.getSplitMap(count,5);
int bindex = 1;
//Callable用于产生结果
List<Callable<List>> tasks = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
//不同的线程用户处理不同分段的数据量,这样就达到了平均分摊查询数据的压力
String[] nums = splitMap.get(String.valueOf(i)).split(":");
int startNum = Integer.valueOf(nums[0]);
int endNum = Integer.valueOf(nums[1]);
Callable<List> qfe = new ThredQuery(startNum, endNum-startNum+1);
tasks.add(qfe);
bindex += bindex;
}
try{
//定义固定长度的线程池 防止线程过多,5就够用了
ExecutorService executorService = Executors.newFixedThreadPool(5);
//Future用于获取结果
List<Future<List>> futures=executorService.invokeAll(tasks);
//处理线程返回结果
if(futures!=null&&futures.size() > 0){
for (Future<List> future:futures){
result.addAll(future.get());
}
}
//关闭线程池,一定不能忘记
executorService.shutdown();
}catch (Exception e){
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("线程查询数据用时:"+(end-start)+"ms");
return result;
}
}
不同的线程负责查询自己线程负责的数据分段的数据方法
也就是你的业务处理方法:
public class ThredQuery implements Callable<List> {
public static SpringContextUtil springContextUtil = new SpringContextUtil();
private int start;
private int end;
//每个线程查询出来的数据集合
private List datas;
public ThredQuery(int start,int end) {
this.start=start;
this.end=end;
//每个线程查询出来的数据集合
QueryService queryService= springContextUtil.getBean("queryService");
List count = queryService.getQueryData(start,end);
datas = count;
}
//返回数据给Future
@Override
public List call() throws Exception {
return datas;
}
}
总结
商品表大概7万多条数据,测试了几次,平均下来,不到3秒的时间,如果不这样写,单线程查询出来的数据大概是9s左右。