前言
手把手教你写一个并行调用模板
1. 一个串行调用的例子
如果让你设计一个APP首页查询的接口,它需要查用户信息、需要查banner
信息、需要查标签信息等等。一般情况,小伙伴会实现如下:
public AppHeadInfoResponse queryAppHeadInfo(AppInfoReq req) {
//查用户信息
UserInfoParam userInfoParam = buildUserParam(req);
UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
//查banner信息
BannerParam bannerParam = buildBannerParam(req);
BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
//查标签信息
LabelParam labelParam = buildLabelParam(req);
LabelDTO labelDTO = labelService.queryLabelInfo(labelParam);
//组装结果
return buildResponse(userInfoDTO,bannerDTO,labelDTO);
}
这段代码会有什么问题嘛?其实这是一段挺正常的代码,但是这个方法实现中,查询用户、banner、标签信息,是串行的。如果查询用户信息耗时200ms
,查询banner信息100ms
,查询标签信息200ms
的话,耗时就是500ms
啦。

其实为了优化性能,我们可以修改为并行调用的方式,耗时可以降为200ms
,如下图所示:

2. CompletionService实现并行调用
对于上面的例子,如何实现并行调用呢?
有小伙伴说,可以使用Future+Callable
实现多个任务的并行调用。但是线程池执行批量任务时,返回值用Future的get()
获取是阻塞的,如果前一个任务执行比较耗时的话,get()
方法会阻塞,形成排队等待的情况。
而CompletionService
是对定义ExecutorService
进行了包装,可以一边生成任务,一边获取任务的返回值。让这两件事分开执行,任务之间不会互相阻塞,可以获取最先完成的任务结果。
CompletionService
的实现原理比较简单,底层通过FutureTask+阻塞队列,实现了任务先完成的话,可优先获取到。也就是说任务执行结果按照完成的先后顺序来排序,先完成可以优先获取到。内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,你调用CompletionService
的poll或take方法即可获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get
方法获取最终的结果。

接下来,我们来看下,如何用CompletionService
,实现并行查询APP首页信息哈。思考步骤如下:
ExecutorService executor = Executors.newFixedThreadPool(10);
//查询用户信息
CompletionService userDTOCompletionService = new ExecutorCompletionService(executor);
Callable userInfoDTOCallableTask = () -> {
UserInfoParam userInfoParam = buildUserParam(req);
return userService.queryUserInfo(userInfoParam);
};
userDTOCompletionService.submit(userInfoDTOCallableTask);
- 如果想把查询
banner
信息的任务,也放到这个线程池的话,发现不好放了,因为返回类型不一样,一个是UserInfoDTO
,另外一个是BannerDTO
。那这时候,我们把泛型声明为Object即可,因为所有对象都是继承于Object的。如下:
ExecutorService executor = Executors.newFixedThreadPool(10);
//查询用户信息
CompletionService