代码优化中关于CountDownLatch和CompletableFuture的使用
背景
做一个功能,在有一批的数据的情况下,处理 内容–>图片,图片–>上传的逻辑。
一个数据集大概几百条数据。需求是可能同时处理几十、几百个数据集。在处理完数据后,需要做汇总处理。
过程
先撸代码在说,一通流水账下来,功能测试正常。就来查看效率了。每次测试相同的数据,几百条记录而已。
初版: 18s
就是基本的for循环,处理完一条记录在去处理下一条。程序本身数据没有出错,就是效率比较慢。
...
for(FileCompareRecordDTO dto : request) {
try {
//如果有
if(!map.containsKey(dto.getFileId())) {
String imageUrl = mathMlUtil.downMathMLToImage(changeMathMlStyle(dto.getMathml()));
String img = mathMlUtil.buildMathML2Img(dto.getMathml(), imageUrl);
map.put(dto.getFileId(), img);
}
}catch(Exception e) {
log.info("");
}
cdl.countDown();
questionIds.add(dto.getQuestionId());
taskId = dto.getTaskId();
}
...
思考
异步处理:因为每次要拿到处理的结果,直接异步肯定不行。
多线程做:尽量保证多个线程的执行时间小于等于单线程的最大时间。
二版:8~9s
因为需要做汇总处理,那首先想到的是使用CountDownLatch工具类。速度是快了一些,但是并没有那么理想。
代码如下:
...
executorService 为自定义线程池
Long taskId = null;
HashMap<Long,String> map = new HashMap<Long,String>();
List<Long> questionIds = new ArrayList<Long>();
CountDownLatch cdl = new CountDownLatch(request.size());
for(FileCompareRecordDTO dto : request) {
executorService.execute(() -> {
try {
String imageUrl = mathMlUtil.downMathMLToImage(changeMathMlStyle(dto.getMathml()));
String img = mathMlUtil.buildMathML2Img(dto.getMathml(), imageUrl);
map.put(dto.getFileId(), img);
}catch(Exception e) {
log.info("");
}
cdl.countDown();
});
questionIds.add(dto.getQuestionId());
taskId = dto.getTaskId();
}
try {
cdl.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("处理信息出错 taskId:{},:{}",taskId,e);
}
...
终版: 2~3s
经新人指点,原来 jdk1.8之后提供了一个功能强大的类,支持异步回调,且线程并行。实际的测试效果要比 CountDownLatch更加明显。后浪果然厉害
executorService 为自定义线程池
List<CompletableFuture<FileUploadDO>> futureList= request.stream().map(dto -> CompletableFuture.supplyAsync(() -> doUploadFile(dto.getFileId(),dto.getMathml()), executorService )).collect(Collectors.toList());
CompletableFuture<Void> completableFuture=CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
completableFuture.join();
Map<Long, String> map = new HashMap<Long,String>();
log.info("futureList get info taskId:{},example:{}",taskId,JSON.toJSONString(futureList.get(0)));
try {
map = futureList.stream().map(y -> {
try {
return y.get();
} catch ( Exception e) {
log.error("replaceQuestionMark method", e);
}
return new FileUploadDO();
}).collect(Collectors.toMap(FileUploadDO::getFileId, FileUploadDO::getImg, (k1, k2) -> k1));
log.info("replaceQuestionMark check taskId:{},map:{}",taskId, JSON.toJSONString(map.keySet()));
} catch ( Exception e) {
log.error("map method", e);
}
//处理上传文件
private FileUploadDO doUploadFile(Long fileId, String mathml) {
FileUploadDO fileUploadDO = new FileUploadDO();
try {
String imageUrl = mathMlUtil.downMathMLToImage(changeMathMlStyle(mathml));
String img = mathMlUtil.buildMathML2Img(mathml, imageUrl);
fileUploadDO.setFileId(fileId);
fileUploadDO.setImg(img);
return fileUploadDO;
} catch (Exception e) {
log.info("doUploadFile error,fileId={},mathml={}", fileId, mathml, e);
}
return fileUploadDO;
}
最终还是要看业务场景的需要,看看jdk本身提供的那些工具类最好用。合适自己的才是最好的
原创文章,转载请标明本文链接: 代码优化中关于CountDownLatch和CompletableFuture的使用
木心
2020年12月22日 18:57
可以试下reactor
admin
2020年12月23日 09:21
可以试试.