CompletableFuture 链式编排:异步编程的优雅解法
Future 太简陋,Callback 太混乱,Reactor 太陡峭。CompletableFuture 是 Java 异步编程的优雅解法。本文拆解它的链式编排和组合操作。
一、为什么需要 CompletableFuture?
java
// 1. Future 的局限: 只能 get(),不能链式操作
Future<String> future = executor.submit(() -> fetchData());
String result = future.get(); // ❌ 阻塞等待!
// 无法在完成后自动执行下一步操作
// 2. Callback 地狱: 回调嵌套
executor.submit(() -> {
String data = fetchData();
executor.submit(() -> {
String processed = processData(data);
executor.submit(() -> {
String saved = saveData(processed);
// 继续嵌套...
});
});
});
// 3. CompletableFuture: 链式编排
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(this::processData)
.thenAccept(this::saveData)
.exceptionally(ex -> { log.error("失败", ex); return null; });
// 优雅!
二、CompletableFuture 基础
java
// 创建 CompletableFuture
// 1. 无参: 已完成的
CompletableFuture<String> completed = CompletableFuture.completedFuture("hello");
// 2. supplyAsync: 异步执行有返回值的任务
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
return fetchData();
});
// 3. runAsync: 异步执行无返回值的任务
CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
doSomething();
});
// 4. 指定线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> fetchData(), pool);
css
CompletableFuture 创建方式:
┌─────────────────────────────────────────────────────┐
│ completedFuture(value) → 已完成的 Future │
│ supplyAsync(supplier) → 异步执行, 有返回值 │
│ runAsync(runnable) → 异步执行, 无返回值 │
│ supplyAsync(supplier, pool) → 指定线程池 │
│ new CompletableFuture() → 手动创建 (可手动 complete) │
└─────────────────────────────────────────────────────┘
手动 complete
java
// 手动控制 CompletableFuture 的完成
CompletableFuture<String> future = new CompletableFuture<>();
// 在另一个线程中完成
new Thread(() -> {
Thread.sleep(1000);
future.complete("done!"); // 手动完成
}).start();
String result = future.get(); // "done!"
三、链式操作
java
// thenApply: 转换结果 (类似 map)
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> f2 = f1.thenApply(s -> s + " world");
// "hello world"
// thenAccept: 消费结果 (无返回值)
CompletableFuture<Void> f3 = f1.thenAccept(s -> System.out.println(s));
// 打印 "hello"
// thenRun: 不关心结果,执行后续操作
CompletableFuture<Void> f4 = f1.thenRun(() -> System.out.println("done"));
// thenCompose: 组合两个依赖的 Future (类似 flatMap)
CompletableFuture<String> f5 = CompletableFuture
.supplyAsync(() -> "userId:123")
.thenCompose(id -> CompletableFuture.supplyAsync(() -> fetchUser(id)));
// 先获取 ID,再用 ID 获取用户
css
链式操作分类:
┌────────────────────────────────────────────────────────┐
│ 同步操作 (在当前线程执行) │
│ ├── thenApply(fn) 转换结果 → CompletableFuture<T> │
│ ├── thenAccept(con) 消费结果 → CompletableFuture<Void> │
│ └── thenRun(run) 忽略结果 → CompletableFuture<Void> │
│ │
│ 异步操作 (在 ForkJoinPool 或指定线程池执行) │
│ ├── thenApplyAsync(fn) │
│ ├── thenAcceptAsync(con) │
│ └── thenRunAsync(run) │
│ │
│ 组合操作 │
│ ├── thenCompose(fn) 串联依赖 → flatMap │
│ ├── thenCombine(other, fn) 合并两个独立结果 │
│ └── applyToEither(other, fn) 取先完成的结果 │
│ │
│ 异常处理 │
│ ├── exceptionally(fn) 捕获异常 → 恢复 │
│ ├── handle(fn) 同时处理正常和异常 │
│ └── whenComplete(fn) 完成后执行(不改变结果) │
└────────────────────────────────────────────────────────┘
thenApply vs thenCompose
java
// thenApply: 转换 (map)
// fn 返回普通值
CompletableFuture<Integer> f = CompletableFuture
.supplyAsync(() -> "123")
.thenApply(s -> Integer.parseInt(s));
// CompletableFuture<Integer>
// thenCompose: 组合 (flatMap)
// fn 返回另一个 CompletableFuture
CompletableFuture<User> f2 = CompletableFuture
.supplyAsync(() -> "userId:123")
.thenCompose(id -> CompletableFuture.supplyAsync(() -> fetchUser(id)));
// CompletableFuture<User> (不是 CompletableFuture<CompletableFuture<User>>)
// 区别:
// thenApply: T → R → CompletableFuture<R>
// thenCompose: T → CompletableFuture<R> → CompletableFuture<R> (扁平化)
四、组合多个 Future
1. thenCombine: 合并两个独立结果
java
// 合并两个独立的 Future
CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> "Tom");
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<String> result = nameFuture.thenCombine(ageFuture, (name, age) -> {
return name + " is " + age + " years old";
});
// "Tom is 20 years old"
2. allOf: 等待全部完成
java
// 等待所有 Future 完成
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> fetchUser());
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> fetchOrder());
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> fetchProduct());
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join(); // 等待全部完成
// allOf 返回 CompletableFuture<Void>,需要手动取结果
String user = f1.join();
String order = f2.join();
String product = f3.join();
java
// 实战: 并行获取多个数据源
public Map<String, Object> fetchAll(Long userId) {
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> userService.findById(userId));
CompletableFuture<List<Order>> orderFuture = CompletableFuture
.supplyAsync(() -> orderService.findByUserId(userId));
CompletableFuture<Account> accountFuture = CompletableFuture
.supplyAsync(() -> accountService.findByUserId(userId));
CompletableFuture.allOf(userFuture, orderFuture, accountFuture).join();
return Map.of(
"user", userFuture.join(),
"orders", orderFuture.join(),
"account", accountFuture.join()
);
}
// 三个请求并行 → 总耗时 = max(3个请求耗时)
// 串行执行 → 总耗时 = 3个请求耗时之和
3. anyOf: 任一完成即返回
java
// 等待任意一个完成
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
Thread.sleep(1000);
return "slow";
});
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
Thread.sleep(100);
return "fast";
});
CompletableFuture<Object> first = CompletableFuture.anyOf(f1, f2);
String result = (String) first.join(); // "fast" (先完成的)
4. applyToEither: 二选一
java
// 从两个 Future 中取先完成的结果并转换
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> fetchFromPrimary());
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> fetchFromSecondary());
CompletableFuture<String> result = f1.applyToEither(f2, data -> process(data));
// 主备容灾: 先返回的数据先处理
五、异常处理
java
// 1. exceptionally: 捕获异常并恢复
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> {
if (error) throw new RuntimeException("fetch failed");
return "data";
})
.exceptionally(ex -> {
log.error("发生异常: {}", ex.getMessage());
return "default"; // 返回默认值
});
// 2. handle: 同时处理正常和异常
CompletableFuture<String> f2 = CompletableFuture
.supplyAsync(() -> "data")
.handle((result, ex) -> {
if (ex != null) {
log.error("异常", ex);
return "fallback";
}
return result.toUpperCase();
});
// 3. whenComplete: 不改变结果,仅执行副作用
CompletableFuture<String> f3 = CompletableFuture
.supplyAsync(() -> "data")
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("失败", ex);
} else {
log.info("成功: {}", result);
}
});
// whenComplete 不改变结果,后续 still 获取原始结果
css
异常处理区别:
exceptionally(ex → fallback):
└── 仅处理异常,正常结果不变
└── 返回 fallback 替代异常
handle((result, ex) → newResult):
└── 同时处理正常和异常
└── 可以改变正常和异常的结果
whenComplete((result, ex) → void):
└── 仅执行副作用 (日志/监控)
└── 不改变结果 (异常仍会传递)
六、超时控制(JDK 9+)
java
// JDK 9+: orTimeout 和 completeOnTimeout
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> fetchData())
.orTimeout(3, TimeUnit.SECONDS); // 3秒超时 → 抛 TimeoutException
// 超时返回默认值
CompletableFuture<String> f2 = CompletableFuture
.supplyAsync(() -> fetchData())
.completeOnTimeout("default", 3, TimeUnit.SECONDS);
// 3秒超时 → 返回 "default"
java
// JDK 8 手动超时控制
public <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout) {
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> timeoutFuture.complete(null), timeout, TimeUnit.MILLISECONDS);
return future.applyToEither(timeoutFuture, Function.identity());
}
七、实际应用场景
1. 并行接口调用
java
// 一个 API 需要聚合多个微服务数据
public class UserDetailService {
public UserDetail getUserDetail(Long userId) {
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> userClient.getUser(userId));
CompletableFuture<List<Order>> ordersFuture = CompletableFuture
.supplyAsync(() -> orderClient.getOrders(userId));
CompletableFuture<Credit> creditFuture = CompletableFuture
.supplyAsync(() -> creditClient.getCredit(userId));
CompletableFuture.allOf(userFuture, ordersFuture, creditFuture)
.join();
return new UserDetail(
userFuture.join(),
ordersFuture.join(),
creditFuture.join()
);
}
}
2. 多级缓存
java
public class MultiLevelCache {
public CompletableFuture<String> get(String key) {
// L1 缓存 → L2 缓存 → 数据库
return CompletableFuture
.supplyAsync(() -> l1Cache.get(key))
.thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
// L1 miss → 查 L2
return CompletableFuture.supplyAsync(() -> l2Cache.get(key));
})
.thenCompose(result -> {
if (result != null) {
// L2 hit → 回填 L1
l1Cache.put(key, result);
return CompletableFuture.completedFuture(result);
}
// L2 miss → 查数据库
return CompletableFuture.supplyAsync(() -> database.query(key));
})
.thenApply(result -> {
if (result != null) {
l1Cache.put(key, result);
l2Cache.put(key, result);
}
return result;
});
}
}
3. 重试机制
java
public <T> CompletableFuture<T> withRetry(Supplier<T> task, int maxRetries) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(task);
for (int i = 0; i < maxRetries; i++) {
future = future.exceptionallyCompose(ex -> {
log.warn("第 {} 次重试", i + 1);
return CompletableFuture.supplyAsync(task);
});
}
return future;
}
八、常见陷阱
1. 默认线程池问题
java
// ❌ 不指定线程池 → 使用 ForkJoinPool.commonPool()
// 所有 CompletableFuture 共享 → 一个任务阻塞影响全局
CompletableFuture.supplyAsync(() -> {
Thread.sleep(10000); // 阻塞 commonPool 线程
return "data";
});
// ✅ 使用自定义线程池
ExecutorService pool = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> {
Thread.sleep(10000);
return "data";
}, pool); // 指定线程池
2. get() 阻塞
java
// ❌ get() 阻塞当前线程
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> fetchData());
String result = f.get(); // 阻塞! 失去异步优势
// ✅ 链式编排,避免阻塞
CompletableFuture.supplyAsync(() -> fetchData())
.thenAccept(data -> process(data));
3. 异常被吞
java
// ❌ 不处理异常 → 异常被吞
CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); });
// 异常被 ForkJoinPool 吞掉!
// ✅ 添加异常处理
CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); })
.exceptionally(ex -> {
log.error("异步任务失败", ex);
return null;
});
九、常见面试追问
Q1:CompletableFuture 和 Future 有什么区别?
Future 只能 get() 阻塞获取结果,不支持链式操作、组合、异常处理。CompletableFuture 支持链式编排(thenApply/thenCompose)、组合(allOf/anyOf)、异常处理(exceptionally)、超时控制(orTimeout)。
Q2:thenApply 和 thenCompose 的区别?
thenApply 是 map 操作(T → R),thenCompose 是 flatMap 操作(T → CompletableFuture)。thenCompose 用于串联多个异步操作,避免嵌套 CompletableFuture。
Q3:CompletableFuture 默认用什么线程池?
ForkJoinPool.commonPool()。它的并行度为 CPU 核心数 - 1。如果有阻塞操作建议使用自定义线程池。
Q4:allOf 返回什么?
返回 CompletableFuture,不包含结果。需要分别调用各 Future 的 join() 获取结果。
十、总结
arduino
CompletableFuture 核心 API:
创建: supplyAsync / runAsync / completedFuture
转换: thenApply (map) / thenCompose (flatMap)
消费: thenAccept / thenRun
组合: thenCombine / allOf / anyOf / applyToEither
异常: exceptionally / handle / whenComplete
超时: orTimeout / completeOnTimeout (JDK 9+)
设计理念:
链式编排 → 避免回调地狱
异步组合 → 并行执行提升吞吐
异常处理 → 优雅恢复
线程池 → 避免默认池阻塞
CompletableFuture 是 Java 异步编程的核心工具。理解链式编排、组合操作和异常处理,就能写出优雅高效的异步代码。在微服务场景中,并行调用多个服务可以大幅提升接口响应速度。