CompletableFuture 学习笔记:异步任务、组合和异常处理

写在前面

我第一次接触 CompletableFuture,是想把几个互不依赖的查询并行执行。后来发现它不只是“开线程”,还涉及线程池、异常处理、任务组合和代码可读性。

这篇是基础学习笔记,只记录我现阶段能讲清楚、也能在项目里用到的部分。

基础用法

有返回值的异步任务:

1
2
3
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "hello";
});

无返回值的异步任务:

1
2
3
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("run task");
});

实际使用时,我更倾向于传入自定义线程池,而不是直接使用默认线程池。

1
2
CompletableFuture<UserInfo> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor);

任务组合

如果几个查询互不依赖,可以用 allOf 等待全部完成:

1
2
3
4
5
6
7
8
9
10
CompletableFuture<UserInfo> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor);

CompletableFuture<UserConfig> configFuture =
CompletableFuture.supplyAsync(() -> configService.getConfig(userId), executor);

CompletableFuture.allOf(userFuture, configFuture).join();

UserInfo user = userFuture.join();
UserConfig config = configFuture.join();

这里要注意,allOf 本身不直接返回每个任务的结果,结果还需要单独取。

异常处理

异步任务里的异常如果不处理,很容易让问题变得不明显。

1
2
3
4
5
6
CompletableFuture<UserInfo> future =
CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor)
.exceptionally(ex -> {
log.warn("query user failed, userId={}", userId, ex);
return null;
});

是否返回默认值,要看业务是否允许。如果是核心数据失败,应该直接返回错误;如果是非核心补充信息,可以考虑降级。

小结

CompletableFuture 适合多个互不依赖任务的组合查询,但不能为了异步而异步。使用时我会重点关注:

  • 是否真的需要并行
  • 是否使用独立线程池
  • 异常有没有处理
  • 代码是否还容易维护

CompletableFuture 学习笔记:异步任务、组合和异常处理
https://zxyblog.top/2024/08/05/CompletableFuture学习笔记-异步任务组合和异常处理/
作者
zxy
发布于
2024年8月5日
许可协议