CompletableFuture
CompletableFuture 是 Java 8 引入的异步编排工具。它比传统 Future 更强,可以表达异步执行、串行转换、并行组合、异常处理和结果回调。
# 1. 基本创建
无返回值:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
sendMessage();
});
有返回值:
CompletableFuture<User> future = CompletableFuture.supplyAsync(() -> {
return queryUser();
});
建议传入自定义线程池:
CompletableFuture.supplyAsync(this::queryUser, executor);
否则默认使用公共 ForkJoinPool,阻塞任务可能影响其他异步任务。
# 2. 串行编排
转换结果:
CompletableFuture<String> nameFuture = userFuture.thenApply(User::getName);
消费结果:
userFuture.thenAccept(user -> log.info("user={}", user));
继续异步任务并扁平化:
CompletableFuture<Order> orderFuture = userFuture.thenCompose(user -> {
return CompletableFuture.supplyAsync(() -> queryOrder(user.id()), executor);
});
thenApply 返回普通值,thenCompose 返回并展开另一个 CompletableFuture。
# 3. 并行组合
两个任务结果合并:
CompletableFuture<User> userFuture = queryUserAsync();
CompletableFuture<Account> accountFuture = queryAccountAsync();
CompletableFuture<UserView> viewFuture = userFuture.thenCombine(accountFuture, UserView::new);
等待全部完成:
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2, future3);
all.join();
任意一个完成:
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
# 4. 异常处理
兜底返回:
CompletableFuture<User> future = queryUserAsync()
.exceptionally(e -> defaultUser());
统一处理成功或失败:
future.handle((result, ex) -> {
if (ex != null) {
return fallback();
}
return result;
});
只观察:
future.whenComplete((result, ex) -> {
if (ex != null) {
log.error("执行失败", ex);
}
});
区别:
| 方法 | 能否改变结果 | 场景 |
|---|---|---|
exceptionally | 能 | 异常兜底 |
handle | 能 | 成功失败都转换 |
whenComplete | 通常不改变 | 记录日志、清理资源 |
# 5. get 与 join
| 方法 | 异常 | 是否受检 |
|---|---|---|
get | ExecutionException | 是 |
join | CompletionException | 否 |
生产代码不要无脑阻塞:
future.get(); // 可能长期等待
应设置超时或做异步链式处理。
# 6. 超时控制
future.orTimeout(1, TimeUnit.SECONDS);
超时返回默认值:
future.completeOnTimeout(defaultValue, 1, TimeUnit.SECONDS);
外部调用必须有超时,避免异步任务永久占用资源。
# 7. 线程池与上下文
带 Async 的方法如果不传线程池,可能使用默认公共池。
thenApply(...)
thenApplyAsync(...)
区别:
| 方法 | 执行线程 |
|---|---|
| 非 Async | 可能在完成前一阶段的线程执行 |
| Async 不传 executor | 默认异步执行器 |
| Async 传 executor | 指定线程池执行 |
ThreadLocal 上下文不会自动传播,需要显式处理。
# 8. 常见坑
- 默认线程池被阻塞任务占满。
- 异常没有处理,链路悄悄失败。
allOf后忘记收集各 Future 结果。- 在异步回调里阻塞等待另一个同池任务导致饥饿。
- 没有超时控制。
- ThreadLocal 上下文丢失。
# 9. 专家实践与边界
CompletableFuture 的核心价值是表达异步依赖关系,而不是把同步代码换个线程跑。优秀的异步编排应该能看出任务之间的依赖、并行、超时和失败策略。
queryUser ─┐
├─ thenCombine -> buildView -> fallback
queryOrder ┘
| 设计点 | 建议 | 风险 |
|---|---|---|
| 线程池 | 阻塞任务传自定义 executor | 公共池被打满 |
| 超时 | orTimeout / completeOnTimeout | 永久等待 |
| 异常 | exceptionally / handle | 异步失败被忽略 |
| 上下文 | 显式传递 trace/user | ThreadLocal 丢失 |
| 阻塞 | 避免同池内 join/get | 线程池饥饿 |
复杂链路建议按业务阶段拆变量,不要写成一条难以调试的长链。每个外部调用都要有超时,每个聚合点都要考虑部分失败。
# Tips 快问快答
Q:CompletableFuture 比 Future 强在哪里? A:支持链式编排、组合、回调和异常处理。
Q:runAsync 和 supplyAsync 区别? A:前者无返回值,后者有返回值。
Q:thenApply 和 thenCompose 区别? A:thenApply 转换普通值,thenCompose 展开嵌套 Future。
Q:allOf 会返回所有结果吗?
A:不会,只返回 CompletableFuture<Void>,结果要从各 Future 取。
Q:默认线程池能随便用吗? A:不建议,阻塞任务应传自定义线程池。
Q:join 和 get 区别? A:join 抛运行时异常,get 抛受检异常。
Q:异步任务需要超时吗? A:生产代码通常需要。
Q:ThreadLocal 会自动传到异步任务吗? A:不会,需要显式传递或包装任务。