在最近遇见的一个业务中,需要调用某开放平台的api,这个api会返回出一个签署列表,签署列表中包含有一个openUserid的属性,这个openUserid包含了用户的实名认证信息,需要一并返回到前端方便展示。因为开放平台没有提供批量请求的方法,所以只能在这个列表中循环调用api来获取需要的数据。
因为在循环里去请求http地址本身就是非常不可靠的,效率会非常非常的慢,这个接口一度需要五六秒的响应时间。因为不能批量请求,所以有一种优化方式就是并行发送请求去请求api。
思路就是创建线程池和一个计数器,使用线程池来并行执行任务,使用 CountDownLatch
来同步所有任务的完成,最后收集结果并处理。
以下是这个的代码示范
1.创建一个列表长度的线程池和计数器,并且创建一个收集结果的list
//使用ExecutorService方式
ExecutorService executorService = Executors.newFixedThreadPool(signTasks.size());
//计数器
CountDownLatch countDownLatch = new CountDownLatch(signTasks.size());
List<Future<IdentityInfoVO>> infoList = new ArrayList<>();
这一段代码的作用是遍历 signTasks
列表中的每个任务,并为每个任务提交一个并行执行的任务到线程池中,在 finally
块中调用 countDownLatch.countDown()
方法,以确保无论任务是否成功完成,计数器都能正确减少。
signTasks.forEach(
task ->{
Future<IdentityInfoVO> identityInfoVO = executorService.submit(() -> {
try {
FadadaSignTaskDO fadadaSignTask = taskIdToSignTaskMap.get(task.getSignTaskId());
return fadadaSignTaskService.getIdentityInfo(fadadaSignTask.getFadadaOpenId());
} finally {
countDownLatch.countDown();
}
});
infoList.add(identityInfoVO);
}
);
3.最后结束后,来收集这些任务的结果,并将结果应用到每个签名任务中
- 创建一个
HashMap
,用于存储用户身份信息,键是OpenUserId
,值是IdentityInfoVO
对象。 - 遍历
infoList
列表中的每个Future<IdentityInfoVO>
对象,调用future.get()
方法获取并行任务的结果。 - 如果获取结果时发生
InterruptedException
或ExecutionException
异常,记录错误日志。 - 如果成功获取到
IdentityInfoVO
对象,将其存入infoVOMap
中,键为identityInfoVO.getOpenUserId()
最后将用户信息设置到签名任务中
- 遍历
signTasks
列表中的每个签名任务signTask
。 - 根据
signTask.getSignTaskId()
获取对应的FadadaSignTaskDO
对象。 - 从
infoVOMap
中获取对应的用户身份信息IdentityInfoVO
对象,并将其设置到signTask
中。
countDownLatch.await();
//获取并行流结果
Map<String, IdentityInfoVO> infoVOMap = new HashMap<>();
for (Future<IdentityInfoVO> future : infoList) {
IdentityInfoVO identityInfoVO = null;
try {
identityInfoVO = future.get();
} catch (InterruptedException | ExecutionException e) {
log.error("获取用户信息失败",e);
}
if (identityInfoVO != null){
infoVOMap.put(identityInfoVO.getOpenUserId(),identityInfoVO);
}
}
signTasks.forEach(signTask -> {
FadadaSignTaskDO fadadaSignTask = taskIdToSignTaskMap.get(signTask.getSignTaskId());
signTask.setUserInfo(infoVOMap.get(fadadaSignTask.getFadadaOpenId()));
});
最后可这样做的好处就是可以充分利用cpu的,提高处理速度,特别是当任务执行时间较长时,并行处理可以显著减少总的执行时间。并行处理可以提高效率,但实际性能提升受多种因素影响。理论上,如果没有资源竞争和其他开销,性能提升接近任务数量倍数,但实际中往往无法达到理论上的10倍提升。
最后附上完整代码
//并行处理
//使用ExecutorService方式
ExecutorService executorService = Executors.newFixedThreadPool(signTasks.size());
//计数器
CountDownLatch countDownLatch = new CountDownLatch(signTasks.size());
List<Future<IdentityInfoVO>> infoList = new ArrayList<>();
signTasks.forEach(
task ->{
Future<IdentityInfoVO> identityInfoVO = executorService.submit(() -> {
try {
FadadaSignTaskDO fadadaSignTask = taskIdToSignTaskMap.get(task.getSignTaskId());
return fadadaSignTaskService.getIdentityInfo(fadadaSignTask.getFadadaOpenId());
} finally {
countDownLatch.countDown();
}
});
infoList.add(identityInfoVO);
}
);
try {
countDownLatch.await();
//获取并行流结果
Map<String, IdentityInfoVO> infoVOMap = new HashMap<>();
for (Future<IdentityInfoVO> future : infoList) {
IdentityInfoVO identityInfoVO = null;
try {
identityInfoVO = future.get();
} catch (InterruptedException | ExecutionException e) {
log.error("获取用户信息失败",e);
}
if (identityInfoVO != null){
infoVOMap.put(identityInfoVO.getOpenUserId(),identityInfoVO);
}
}
signTasks.forEach(signTask -> {
FadadaSignTaskDO fadadaSignTask = taskIdToSignTaskMap.get(signTask.getSignTaskId());
signTask.setUserInfo(infoVOMap.get(fadadaSignTask.getFadadaOpenId()));
});
} catch (Exception e) {
log.error("获取链接失败", e);
}