CompletableFuture两种写法疑问:执行逻辑与最优方案选择
CompletableFuture异步依赖逻辑的疑问与分析
我有两段Java代码,均实现依赖异步结果的业务逻辑:
第一段代码
CompletableFuture<ApplicationUserDto> byUserName = applicationUserService .findByUserName(getCurrentApplicationUserName()); CompletableFuture<List<FocusDto>> focuses = focusService.findMine(byUserName.join().getFacilities()); CompletableFuture.allOf(byUserName, focuses).join(); Iterable<IdmDataFacade> facilities = idmService.lookupFacilities(new HashSet<>(byUserName.join().getFacilities())).toIterable();
其中focusService.findMine依赖applicationUserService.findByUserName的返回结果,前端测试显示数据正常。
第二段代码(使用thenComposeAsync)
CompletableFuture<ApplicationUserDto> byUserName = applicationUserService .findByUserName(getCurrentApplicationUserName()); CompletableFuture<List<FocusDto>> focuses = byUserName .thenComposeAsync((result) -> focusService.findMine(result.getFacilities())); CompletableFuture.allOf(byUserName, focuses).join(); Iterable<IdmDataFacade> facilities = idmService.lookupFacilities(new HashSet<>(byUserName.join().getFacilities())).toIterable();
两段代码运行结果一致,线程使用情况也相同,现咨询两个问题:
- CompletableFuture是否会自动等待
byUserName完成,再为focusService.findMine填充参数? - 哪段代码的实现更优?
问题解答
1. 关于异步等待的逻辑
- 第一段代码里,
byUserName.join()会直接阻塞当前线程,直到byUserName执行完成并返回结果,所以focusService.findMine的参数确实是在byUserName完成后才填充的,但这是手动阻塞实现的,并非CompletableFuture自动处理。 - 第二段代码用的
thenComposeAsync是CompletableFuture专为异步依赖场景设计的API,它会自动监听上游byUserName的完成状态,只有当byUserName执行完成后,才会调用传入的函数执行focusService.findMine,整个过程异步非阻塞,不需要手动调用join()阻塞线程。
2. 代码实现优劣对比
第二段代码的实现更优,核心原因如下:
- 避免无意义阻塞:第一段的
join()把异步调用强行变成同步执行,完全浪费了CompletableFuture的异步特性,线程会被闲置等待结果;而第二段的链式调用是纯异步流程,线程可以在等待期间处理其他任务,资源利用率更高。 - 逻辑表达更清晰:
thenComposeAsync明确体现了“先完成用户查询,再用用户结果执行关注列表查询”的依赖关系,代码意图一目了然,后续扩展异步步骤也更方便。 - 高并发场景下的性能优势:虽然测试时线程使用情况相同,但在高并发场景中,第一段的阻塞会导致线程池资源被快速耗尽,无法处理更多请求;第二段的异步编排能让线程及时释放,支撑更高的并发量。
内容的提问来源于stack exchange,提问作者Thomas Lang
相关产品推荐
相关产品推荐

