CompletableFuture 是 Java 8 引入的一个极其强大的异步编程工具。如果说我们上一轮聊的 Spring 事件广播(@Async)是"发个通知让别人去干活",那么 CompletableFuture 就是"主动把任务丢给后台线程,并且能优雅地把多个后台任务的结果像搭积木一样拼装起来"。
它实现了 Future 和 CompletionStage 接口,彻底解决了传统 Future 只能阻塞等待(get())、无法链式处理、异常处理繁琐等痛点。
一、 核心应用场景
在 Spring Boot 开发中,CompletableFuture 主要用于以下四大场景:
- 并行数据聚合(最常用) :
比如一个"商品详情页"接口,需要同时查询商品信息、用户信息、订单信息、优惠券信息。使用CompletableFuture可以并行调用这四个接口,最后汇总结果,将原本串行的耗时(如 4 秒)缩短为最慢那个接口的耗时(如 1 秒)。 - 任务串行依赖(流水线) :
比如:第一步异步获取用户 ID -> 第二步根据 ID 异步查询用户详情 -> 第三步组装欢迎信息。前一步的输出直接作为后一步的输入,全程非阻塞。 - 事件驱动(非阻塞回调) :
耗时操作(如文件上传、复杂计算)在后台执行,主线程不被阻塞。任务完成后,自动触发回调(如更新界面、发送通知、记录日志)。 - 竞速与容错(高可用) :
同时向两个不同的数据源发起查询,谁先返回结果就用谁的(anyOf);或者当主服务异常时,提供默认的兜底返回值。
二、 核心用法与实战代码
1. 创建异步任务
supplyAsync:有返回值的异步任务(最常用)。runAsync:无返回值的异步任务(如发通知、记日志)。
java
编辑
1// 1. 有返回值(默认使用 ForkJoinPool,建议传入自定义线程池)
2CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
3 // 模拟耗时操作
4 return "查询到的数据";
5}, customExecutor);
6
7// 2. 无返回值
8CompletableFuture.runAsync(() -> {
9 System.out.println("后台默默执行任务...");
10}, customExecutor);
2. 串行处理(链式调用)
thenApply:接收上一步结果,处理后返回新值(相当于 Stream 的map)。thenAccept:接收上一步结果,消费掉,无返回值(相当于 Stream 的forEach)。thenRun:不关心上一步结果,上一步完成后直接执行新任务。
java
编辑
1CompletableFuture.supplyAsync(() -> "User123", customExecutor)
2 .thenApply(userId -> userId + " 的详细信息") // 处理结果并返回
3 .thenAccept(userInfo -> System.out.println("获取到: " + userInfo)) // 消费结果
4 .thenRun(() -> System.out.println("整个流程结束")); // 收尾工作
3. 并行组合(核心威力)
allOf:等待所有任务完成。thenCombine:两个独立任务都完成后,合并它们的结果。
java
编辑
1// 并行查询两个服务
2CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> "张三", customExecutor);
3CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(() -> 25, customExecutor);
4
5// 等两个都完成后,合并结果
6userFuture.thenCombine(ageFuture, (name, age) -> name + " 今年 " + age + " 岁")
7 .thenAccept(System.out::println); // 输出:张三 今年 25 岁
4. 异常处理(优雅降级)
exceptionally:相当于catch,发生异常时返回一个兜底默认值。handle:无论成功还是异常都会执行,可以统一处理。
java
编辑
1CompletableFuture.supplyAsync(() -> {
2 if (Math.random() > 0.5) throw new RuntimeException("服务挂了!");
3 return "Success";
4}, customExecutor)
5.exceptionally(ex -> {
6 // 发生异常时,返回默认值,防止整个链路崩溃
7 return "Default Value";
8})
9.thenAccept(result -> System.out.println("最终结果: " + result));
三、 ⚠️ 架构师避坑指南(极其重要)
在实际企业级开发中,使用 CompletableFuture 必须注意以下几点:
- 绝对不要使用默认的
ForkJoinPool.commonPool():
如果不指定线程池,CompletableFuture会使用全局默认的ForkJoinPool。如果某个任务发生了阻塞(如查数据库),会把整个线程池耗尽,导致系统中所有使用默认池的异步任务全部卡死。务必传入自定义的ThreadPoolExecutor! - 避免过度异步 :
如果任务本身执行极快(比如只是内存中的简单计算),异步化带来的"线程切换开销"反而比直接执行还要慢。只有**耗时操作(IO、网络请求、复杂计算)**才值得异步化。 - 注意内存泄漏 :
如果异步任务持有大对象(如巨大的 List、字节数组),且任务长期未完成,会导致这些大对象无法被 GC 回收,最终引发 OOM。任务完成后应及时释放资源。 - 必须设置超时时间 :
调用外部服务时,一定要防止任务永久挂起。可以使用orTimeout()或completeOnTimeout()等方法进行超时控制。