大家好,今天我来给大家讲解一下CompletableFuture,从基础使用到底层实现

前言
在 Java 的并发编程演进史中,
Future的出现是一个里程碑,它允许我们异步获取计算结果。然而,Java 5 引入的Future存在显著的局限性:它很难在不阻塞主线程的情况下组合多个异步任务,且缺乏优雅的回调机制。
一、CompletableFuture是什么?
前言提到Future在异步处理计算结果的功能上存在局限性,需要阻塞主线程才能组合多个异步结果,并且没有优雅的回调机制,为了解决这个问题,JDK1.8引入了CompletableFuture。
Java 8 引入了神器
CompletableFuture。它实现了Future和CompletionStage接口,不仅提供了强大的函数式编程能力,还构建了一套完整的异步任务编排体系。
二、CompletableFuture的基本使用
2.1 异步任务的发起
通常异步任务不需要我们手动new,直接使用CompletableFuture的工厂方法即可
java
// 1. 无返回值的异步任务 (runAsync)
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {
System.out.println("执行无返回值的异步任务: " + Thread.currentThread().getName());
});
// 2. 有返回值的异步任务 (supplyAsync)
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("执行有返回值的异步任务: " + Thread.currentThread().getName());
return "Hello World";
});
对于提交的任务会提交到ForkJoinPool.CommonPool()去异步执行。
2.2 链式流转:thenApply, thenAccept, thenRun
任务完成后的回调,根据参数和返回值的不同分为三类:
-
thenApply(变换): 拿到上一步结果,处理后返回新结果(Function: T -> R)。 -
thenAccept(消费): 拿到上一步结果,处理后不返回(Consumer: T -> Void)。 -
thenRun(触发): 不关心上一步结果,只是触发下一步(Runnable: Void -> Void)。
java
CompletableFuture.supplyAsync(() -> "Java")
.thenApply(s -> s + " 8") // 变换: "Java" -> "Java 8"
.thenAccept(s -> System.out.println("Result: " + s)) // 消费
.thenRun(() -> System.out.println("Finished!")); // 触发
2.3 任务组合
CompletableFuture可以很好的避免回调地狱,并且代码简洁可读性高
2.3.1 两个独立任务的组合 (AND 关系) -> thenCombine
当任务 A 和任务 B 都完成后,执行任务 C。
java
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
// 模拟耗时
return 10;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
return 20;
});
// 合并结果: 10 + 20
CompletableFuture<Integer> result = future1.thenCombine(future2, (res1, res2) -> res1 + res2);
2.3.2 两个前后依赖任务的组合 (串联) -> thenCompose
类似于 Stream 流中的 flatMap。通过上一个任务的结果,生成下一个 CompletableFuture。
java
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> "User123")
.thenCompose(userId -> {
// 使用 userId 去查询详细信息,返回一个新的 Future
return UserService.getUserDetailAsync(userId);
});
2.3.3 多任务聚合 -> allOf / anyOf
-
allOf: 等待所有任务完成(常用于并行查询多个接口,最后统一组装)。 -
anyOf: 只要有一个任务完成就返回(常用于备份链路查询,谁快用谁)。
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class AllOfExample {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
System.out.println("--- 详情页数据加载开始 ---");
// 1. 异步任务:获取商品基本信息
CompletableFuture<String> infoFuture = CompletableFuture.supplyAsync(() -> {
sleep(1); // 模拟耗时
System.out.println("商品信息查询完成");
return "iPhone 15 Pro";
});
// 2. 异步任务:获取评论数据
CompletableFuture<Integer> commentFuture = CompletableFuture.supplyAsync(() -> {
sleep(2);
System.out.println("评论统计查询完成");
return 9999;
});
// 3. 异步任务:获取推荐商品
CompletableFuture<String> recommendFuture = CompletableFuture.supplyAsync(() -> {
sleep(3);
System.out.println("推荐商品查询完成");
return "AirPods Pro 2";
});
// 4. allOf 编排:等待所有任务完成
// 注意:allOf 返回的是 CompletableFuture<Void>,它不持有结果
CompletableFuture<Void> allFutures = CompletableFuture.allOf(infoFuture, commentFuture, recommendFuture);
// 5. 等待完成并提取结果
// 这里使用 join() 阻塞主线程,直到 allFutures 完成
allFutures.join();
// 6. 此时三个任务肯定都完成了,可以直接 get/join 获取结果,不会再阻塞
String info = infoFuture.join();
Integer comments = commentFuture.join();
String recommend = recommendFuture.join();
long endTime = System.currentTimeMillis();
System.out.println("--- 详情页加载完毕 ---");
System.out.println("总耗时: " + (endTime - startTime) / 1000 + "秒");
System.out.println("结果聚合: [" + info + ", 评论数:" + comments + ", 推荐:" + recommend + "]");
}
private static void sleep(int seconds) {
try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AnyOfExample {
public static void main(String[] args) {
System.out.println("--- 开始双路查询 ---");
// 1. 任务A:快线路 (模拟 1秒)
CompletableFuture<String> fastService = CompletableFuture.supplyAsync(() -> {
sleep(1);
System.out.println("Fast Service 响应");
return "来自快速服务的数据";
});
// 2. 任务B:慢线路 (模拟 3秒)
CompletableFuture<String> slowService = CompletableFuture.supplyAsync(() -> {
sleep(3);
System.out.println("Slow Service 响应");
return "来自慢速服务的数据";
});
// 3. anyOf 竞速
// 只要有一个完成(无论是正常完成还是异常),anyOf 就会完成
CompletableFuture<Object> winnerFuture = CompletableFuture.anyOf(fastService, slowService);
// 4. 获取结果
// 注意:anyOf 返回的是 Object,因为参与竞速的任务可能有不同的返回类型
Object result = winnerFuture.join();
System.out.println("最终采纳结果: " + result);
}
private static void sleep(int seconds) {
try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
2.3.4 优雅的异常处理
不再需要 try-catch 包裹整个逻辑块:
-
exceptionally: 仅当发生异常时触发,提供降级值。 -
handle: 无论成功失败都会触发,可以同时访问结果和异常。
java
CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("DB Error");
return "Success";
}).exceptionally(ex -> {
System.out.println("发生异常: " + ex.getMessage());
return "Default Value"; // 降级策略
});
三、底层实现
3.1. 核心数据结构
CompletableFuture 内部维护了两个核心关键点:
-
result(volatile Object): 存储任务的执行结果或异常。-
如果任务未完成,它是
null(或者特定的ALT标记)。 -
如果任务完成,它是具体的值。
-
如果异常,它被封装为
AltResult。
-
-
stack(volatile Completion) : 这是实现链式调用的关键。它是一个无锁的 Treiber Stack(驱动栈)。
3.2. Completion 对象:观察者模式的变体
当你调用 future.thenApply(...) 时,由于前一个任务可能还没完成,主线程不会阻塞等待。相反,它会将你的回调逻辑封装成一个 Completion 对象(继承自 ForkJoinTask),然后通过 CAS 操作压入 stack 栈顶。
每一个 Completion 对象都包含:
-
Executor: 该回调将在哪个线程池执行。
-
Dep : 下一步产生的新的
CompletableFuture。 -
Src : 当前依赖的上游
CompletableFuture。 -
Action: 具体的执行逻辑(如 Function, Consumer)。
3.3. 状态流转与通知机制 (Observer Pattern + CAS)
当上游任务执行完毕(调用 complete() 或 completeExceptionally())时,会触发以下流程:
-
CAS 更新状态 : 使用
Unsafe.compareAndSwapObject将result字段从 null 更新为结果值。 -
触发回调 (
postComplete): 这是核心驱动引擎。-
当前线程会检测
stack是否为空。 -
如果不为空,通过 CAS 弹出栈顶的
Completion对象。 -
执行该对象的
tryFire()方法。 -
tryFire()内部会执行用户的回调逻辑,并将结果填充到下一个CompletableFuture中,从而产生连锁反应(Domino Effect)。
-
也就是说我们的CompletableFuture里面维护了一个Completion链,这个链子上有多个Completion,每个Completion代表一个异步任务,并且每个Completion可以依赖它的前后任务的结果,对于提交的任务,一般交给Java的共有的ForkJoinPool的线程去异步执行。
当一个Completion阶段执行完后CompletableFuture会去触发它的下一个与之关联的Completion阶段。
总结
CompletableFuture提供了一组简洁实现异步、任务组合关联的API,这在我们的代码使用可以以可读性高的代码表示含义!
