#java/并发 #难度/进阶 #类型/笔记
本文回答什么问题 :JDK 8 CompletableFuture 如何用链式 API 解决 Future.get() 阻塞 + 回调地狱,覆盖 6 大 API、4 种组合模式与异常处理最佳实践? 预计阅读时间 :20 分钟
源码版本 :OpenJDK 17(
java.util.concurrent.CompletableFuture+CompletionStage)
JDK 8 引入的 CompletableFuture 不仅是 Future 接口的实现,更是 Java 异步编程的事实标准。它把回调、组合、异常处理、超时控制全部串成链式 API,彻底告别回调地狱。本文用 6 个可执行 Demo + 4 张时序/流程图,把 supplyAsync / thenApply / thenCompose / thenCombine / exceptionally / orTimeout 这 6 大 API 一次性讲透。
核心要点速查(建议收藏)
- JDK 8 引入
CompletableFuture,JDK 9+ 新增orTimeout/completeOnTimeout超时控制,目前生产推荐使用 JDK 17 LTS。 supplyAsync(Supplier)与supplyAsync(Supplier, Executor)共 4 种创建方式 ,必须指定自定义线程池,严禁依赖ForkJoinPool.commonPool()(守护线程 + 线程数 = CPU-1 + 与 parallelStream 共享)。thenApply返回CompletableFuture<CompletableFuture<T>>(嵌套) vsthenCompose返回CompletableFuture<T>(扁平化) ------有异步调用必须用thenCompose。- 组合 3 件套:
thenCombine(并行汇合) /thenAcceptBoth(双消费) /applyToEither(竞争取最快) ------分别对应"等所有"、"消费所有"、"取最快"三种语义。 - 异常处理 3 件套:
exceptionally(兜底返回默认)/handle(转换结果或异常)/whenComplete(回调不改值) ------链式调用必须exceptionally兜底,否则异常会"吞噬"后续 stage。 CompletableFuture本身线程安全,但任务内的共享状态必须用AtomicInteger等同步------本文 Demo 6 是经典的「订单+库存+用户」并行查询实战模板。
📋 概述
一句话:CompletableFuture 是 JDK 8 引入的"异步编排神器",让你像搭积木一样组合多个异步任务,告别 Future.get() 的阻塞等待。
为什么需要 CompletableFuture?
在 Java 中,我们经常需要同时执行多个异步任务(比如同时查询用户信息、订单详情、库存数据)。传统的 Future 接口有两个痛点:
get()会阻塞 :调用future.get()时,当前线程会一直等待直到任务完成,失去了异步的意义。- 无法链式组合:多个异步任务之间有依赖关系时(比如先查用户 ID,再用用户 ID 查订单),代码会变成嵌套的回调地狱。
CompletableFuture 解决了这两个问题,它支持:
- 非阻塞获取结果 :通过
thenApply、thenAccept等链式 API - 任务组合 :
thenCombine(并行组合)、thenCompose(串行组合) - 异常处理 :
exceptionally、handle、whenComplete - 超时控制 :JDK 9+ 的
orTimeout、completeOnTimeout

💡 生活类比:外卖接力系统
把异步任务想象成外卖订单 ,CompletableFuture 就是外卖接力系统:
| 外卖流程 | 异步任务 | 说明 |
|---|---|---|
| 下单 | supplyAsync(() -> createOrder()) |
创建异步任务 |
| 接单 | thenApply(order -> pack(order)) |
订单完成后打包 |
| 配送 | thenAccept(packed -> deliver(packed)) |
打包完成后配送 |
| 完成 | thenRun(() -> notifyUser()) |
通知用户 |
每个环节都是异步 的,可以串行 (下单→接单→配送)也可以并行(同时下单多个外卖)。

🔍 创建异步任务
CompletableFuture 提供 4 种创建方式:
| 方法 | 返回值 | 用途 | 是否指定线程池 |
|---|---|---|---|
supplyAsync(Supplier) |
CompletableFuture<T> |
有返回值的异步任务 | 否(用 ForkJoinPool.commonPool()) |
supplyAsync(Supplier, Executor) |
CompletableFuture<T> |
有返回值的异步任务 | 是 |
runAsync(Runnable) |
CompletableFuture<Void> |
无返回值的异步任务 | 否 |
runAsync(Runnable, Executor) |
CompletableFuture<Void> |
无返回值的异步任务 | 是 |
线程池选择
ini
// ❌ 默认使用 ForkJoinPool.commonPool(),可能被 parallelStream 共享
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");
// ✅ 指定自定义线程池,隔离业务
ExecutorService bizPool = Executors.newFixedThreadPool(8);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "hello", bizPool);
为什么必须指定线程池?
- ForkJoinPool.commonPool() 是守护线程,主线程结束时 JVM 可能退出
- 线程数 = CPU 核数 - 1,IO 密集场景不够用
- 与 parallelStream 共享,互相影响
🔍 链式转换
链式转换是 CompletableFuture 的核心能力,让异步任务像流水线一样串联。
thenApply vs thenAccept vs thenRun
| 方法 | 输入 | 输出 | 用途 |
|---|---|---|---|
thenApply(Function) |
T | U | 转换结果 |
thenAccept(Consumer) |
T | void | 消费结果 |
thenRun(Runnable) |
无 | void | 执行动作 |
arduino
// thenApply:转换结果
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<Integer> lengthFuture = future.thenApply(String::length); // 5
// thenAccept:消费结果(无返回值)
future.thenAccept(s -> System.out.println("结果: " + s));
// thenRun:执行动作(不关心前一步结果)
future.thenRun(() -> System.out.println("任务完成"));
thenCompose vs thenApply
arduino
// thenApply:返回 CompletableFuture<CompletableFuture<T>>(嵌套)
CompletableFuture<CompletableFuture<String>> nested = future
.thenApply(id -> queryUser(id)); // ❌ 嵌套了
// thenCompose:返回 CompletableFuture<T>(扁平化)
CompletableFuture<String> flat = future
.thenCompose(id -> queryUser(id)); // ✅ 扁平化
关键区别:
thenApply:同步转换,返回值是CompletableFuture<U>thenCompose:异步组合,返回值是CompletableFuture<T>,适合链式异步调用
链式调用时序图

🔍 组合任务
CompletableFuture 支持多种任务组合模式:
thenCombine:两任务并行组合
ini
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> "张三");
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> "订单123");
// 两个任务都完成后组合结果
CompletableFuture<String> result = userFuture.thenCombine(orderFuture,
(user, order) -> user + "的" + order);
System.out.println(result.join()); // 张三的订单123
thenAcceptBoth / runAfterBoth
csharp
// thenAcceptBoth:两个任务都完成后消费结果
userFuture.thenAcceptBoth(orderFuture, (user, order) -> {
System.out.println("用户: " + user + ", 订单: " + order);
});
// runAfterBoth:两个任务都完成后执行动作
userFuture.runAfterBoth(orderFuture, () -> {
System.out.println("两个任务都完成了");
});
applyToEither / acceptEither(竞争取最快)
ini
CompletableFuture<String> source1 = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "来源1";
});
CompletableFuture<String> source2 = CompletableFuture.supplyAsync(() -> {
sleep(500);
return "来源2";
});
// 取最快完成的任务结果
String fastest = source1.applyToEither(source2, s -> "最快: " + s).join();
System.out.println(fastest); // 最快: 来源2
组合模式流程图

🔍 异常处理
异步任务中异常处理至关重要,CompletableFuture 提供三种方式:
exceptionally:捕获异常,返回默认值
arduino
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("出错了");
return "正常结果";
});
CompletableFuture<String> safe = future.exceptionally(ex -> {
System.out.println("捕获异常: " + ex.getMessage());
return "默认值"; // 异常时返回默认值
});
System.out.println(safe.join()); // 默认值
handle:处理结果/异常,可改变返回值
kotlin
CompletableFuture<String> handled = future.handle((result, ex) -> {
if (ex != null) {
return "异常处理: " + ex.getMessage();
}
return "正常: " + result;
});
System.out.println(handled.join()); // 异常处理: 出错了
whenComplete:完成回调,不改变结果
csharp
future.whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("任务失败: " + ex.getMessage());
} else {
System.out.println("任务成功: " + result);
}
});
// 结果仍然是异常,whenComplete 不改变结果
异常处理时序图

🔍 CompletableFuture vs Future
| 特性 | Future | CompletableFuture |
|---|---|---|
| 阻塞获取 | get() 阻塞 |
join() 非阻塞(但会抛异常) |
| 链式调用 | ❌ 不支持 | ✅ thenApply/thenAccept/thenRun |
| 任务组合 | ❌ 不支持 | ✅ thenCombine/thenCompose |
| 异常处理 | ❌ 只能 try-catch | ✅ exceptionally/handle/whenComplete |
| 超时控制 | get(timeout, unit) |
orTimeout/completeOnTimeout(JDK 9+) |
| 手动完成 | ❌ 不支持 | ✅ complete/completeExceptionally |
| 异步回调 | ❌ 不支持 | ✅ thenApplyAsync/thenAcceptAsync |
💻 可跑代码
Demo 1:基本异步任务
arduino
import java.util.concurrent.CompletableFuture;
public class BasicAsyncDemo {
public static void main(String[] args) {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Hello, CompletableFuture!";
});
// 非阻塞获取结果
System.out.println("任务已提交,主线程继续执行...");
// 方式1:join()(推荐,会抛CompletionException)
String result = future.join();
System.out.println("结果: " + result);
// 方式2:get()(需要处理异常)
// String result2 = future.get(); // 抛 InterruptedException, ExecutionException
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
预期输出:
makefile
任务已提交,主线程继续执行...
结果: Hello, CompletableFuture!
Demo 2:链式 thenApply + thenAccept
csharp
import java.util.concurrent.CompletableFuture;
public class ChainDemo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println("1. 查询用户ID");
return 1001;
})
.thenApply(userId -> {
System.out.println("2. 根据ID查询用户名,userId=" + userId);
return "张三";
})
.thenAccept(userName -> {
System.out.println("3. 打印用户名: " + userName);
})
.join();
}
}
预期输出:
markdown
1. 查询用户ID
2. 根据ID查询用户名,userId=1001
3. 打印用户名: 张三
Demo 3:thenCombine 两任务组合
typescript
import java.util.concurrent.CompletableFuture;
public class CombineDemo {
public static void main(String[] args) {
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
sleep(500);
return "张三";
});
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
sleep(300);
return "订单123";
});
// 两个任务都完成后组合结果
CompletableFuture<String> result = userFuture.thenCombine(orderFuture,
(user, order) -> user + "的" + order);
System.out.println("组合结果: " + result.join());
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
预期输出:
makefile
组合结果: 张三的订单123
Demo 4:thenCompose 扁平化
csharp
import java.util.concurrent.CompletableFuture;
public class ComposeDemo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println("1. 获取用户ID");
return 1001;
})
.thenCompose(userId -> {
// thenCompose 避免了 CompletableFuture<CompletableFuture<T>> 嵌套
System.out.println("2. 根据ID查询用户详情,userId=" + userId);
return CompletableFuture.supplyAsync(() -> "用户: 张三, ID: " + userId);
})
.thenAccept(userDetail -> {
System.out.println("3. 打印详情: " + userDetail);
})
.join();
}
}
预期输出:
markdown
1. 获取用户ID
2. 根据ID查询用户详情,userId=1001
3. 打印详情: 用户: 张三, ID: 1001
Demo 5:exceptionally 异常处理
arduino
import java.util.concurrent.CompletableFuture;
public class ExceptionDemo {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行任务...");
if (true) throw new RuntimeException("任务执行失败!");
return "正常结果";
})
.exceptionally(ex -> {
System.out.println("捕获异常: " + ex.getMessage());
return "默认值";
});
System.out.println("结果: " + future.join());
}
}
预期输出:
makefile
执行任务...
捕获异常: java.lang.RuntimeException: 任务执行失败!
结果: 默认值
Demo 6:实战 - 异步查询订单+库存+用户
ini
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class OrderQueryDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
// 三个独立查询并行执行
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
sleep(300);
return "张三";
}, pool);
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
sleep(200);
return "订单#123";
}, pool);
CompletableFuture<Integer> stockFuture = CompletableFuture.supplyAsync(() -> {
sleep(400);
return 10;
}, pool);
// 等待全部完成后组合结果
CompletableFuture<String> result = CompletableFuture.allOf(userFuture, orderFuture, stockFuture)
.thenApply(v -> {
String user = userFuture.join();
String order = orderFuture.join();
int stock = stockFuture.join();
return String.format("用户: %s, 订单: %s, 库存: %d", user, order, stock);
});
System.out.println("查询结果: " + result.join());
pool.shutdown();
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { }
}
}
预期输出:
bash
查询结果: 用户: 张三, 订单: 订单#123, 库存: 10
⚠️ 常见问题与踩坑
Q1: get() 会阻塞吗?用哪个方法更好?
A: get() 会阻塞当前线程直到任务完成。推荐使用 join() :
join():不抛 checked exception,抛CompletionExceptionget():抛InterruptedException和ExecutionException
arduino
// ❌ 阻塞且需要处理 checked exception
try {
String result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// ✅ 推荐:不阻塞(异步场景),异常包装为 CompletionException
String result = future.join();
Q2: 线程池用哪个?ForkJoinPool.commonPool vs 自定义?
A: 必须使用自定义线程池,原因:
- ForkJoinPool.commonPool() 是守护线程,主线程结束时 JVM 可能退出
- 线程数 = CPU 核数 - 1,IO 密集场景不够用
- 与 parallelStream 共享,互相影响
scss
// ❌ 默认公共池
CompletableFuture.supplyAsync(() -> biz());
// ✅ 自定义线程池
ExecutorService bizPool = Executors.newFixedThreadPool(8);
CompletableFuture.supplyAsync(() -> biz(), bizPool);
Q3: 异常丢失怎么办?
A: 链式调用中,异常会"吞噬"后续 stage。必须用 exceptionally 或 handle 兜底:
php
// ❌ 异常丢失:后续 thenApply 不执行
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("出错");
})
.thenApply(r -> "正常") // 不执行
.thenAccept(System.out::println); // 不执行
// ✅ 异常处理:exceptionally 兜底
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("出错");
})
.thenApply(r -> "正常")
.exceptionally(ex -> "默认值") // 捕获异常
.thenAccept(System.out::println); // 执行
Q4: 超时怎么控制?
A: JDK 9+ 提供两种方式:
rust
// 方式1:orTimeout - 超时抛异常
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
sleep(5000);
return "结果";
}).orTimeout(2, TimeUnit.SECONDS); // 2秒后抛 TimeoutException
// 方式2:completeOnTimeout - 超时用默认值
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
sleep(5000);
return "结果";
}).completeOnTimeout("默认值", 2, TimeUnit.SECONDS); // 2秒后返回默认值
Q5: CompletableFuture 线程安全吗?
A: CompletableFuture 本身是线程安全的 ,但任务中的共享状态不是:
scss
// ❌ 共享状态不是线程安全
int[] count = {0};
CompletableFuture.runAsync(() -> count[0]++);
CompletableFuture.runAsync(() -> count[0]++);
// 结果可能是 1 或 2,不确定
// ✅ 使用原子类
AtomicInteger atomicCount = new AtomicInteger(0);
CompletableFuture.runAsync(() -> atomicCount.incrementAndGet());
CompletableFuture.runAsync(() -> atomicCount.incrementAndGet());
// 结果一定是 2
🎯 最佳实践
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| 指定自定义线程池 | 使用默认 ForkJoinPool.commonPool() |
| 用 join() 替代 get() | 在异步上下文中调用 get() |
| 异常处理兜底 | 忽略异常导致任务丢失 |
| 超时控制 | 无限等待任务完成 |
| 扁平化用 thenCompose | thenApply 返回嵌套 CompletableFuture |
实战模式表
| 模式 | 场景 | 示例 |
|---|---|---|
| 串行执行 | 任务有依赖关系 | thenCompose |
| 并行执行 | 独立任务同时进行 | supplyAsync + thenCombine |
| 竞争取最快 | 多源冗余 | applyToEither |
| 全部完成 | 汇总多个任务结果 | allOf |
| 任一完成 | 取最快结果 | anyOf |
💡 面试要点
-
CompletableFuture 与 Future 的区别?
- Future 只能阻塞获取结果,CompletableFuture 支持链式调用、任务组合、异常处理
-
thenApply 和 thenCompose 的区别?
- thenApply 用于同步转换,thenCompose 用于异步组合(避免嵌套)
-
为什么必须指定线程池?
- 默认 ForkJoinPool.commonPool() 是守护线程,可能被 parallelStream 共享,线程数不够
-
如何处理异步任务中的异常?
- exceptionally:捕获异常返回默认值
- handle:处理结果/异常,可改变返回值
- whenComplete:完成回调,不改变结果
-
CompletableFuture 线程安全吗?
- 本身线程安全,但任务中的共享状态需要额外同步(用原子类或锁)
-
如何控制超时?
- JDK 9+:orTimeout(抛异常)、completeOnTimeout(返回默认值)
- JDK 8:future.get(timeout, unit)
-
allOf 和 anyOf 的区别?
- allOf:所有任务完成后返回 Void
- anyOf:任一任务完成后返回结果
📝 总结
| 要点 | 记住这一句 |
|---|---|
| 核心价值 | 链式调用 + 任务组合 + 异常处理 |
| 创建任务 | supplyAsync(有返回值)、runAsync(无返回值) |
| 链式转换 | thenApply(转换)、thenAccept(消费)、thenRun(执行) |
| 任务组合 | thenCombine(并行)、thenCompose(串行)、applyToEither(竞争) |
| 异常处理 | exceptionally(兜底)、handle(转换)、whenComplete(回调) |
| 线程池 | 必须指定自定义线程池,避免 commonPool() |
| 超时控制 | JDK 9+ 用 orTimeout/completeOnTimeout |
📖 外部参考
本文要点速查
CompletableFuture=Future+ 回调 + 组合 + 异常处理 + 超时控制 的"全家桶",JDK 8 引入、JDK 9+ 增强。- 链式 4 件套:
supplyAsync→thenApply/thenCompose→thenCombine/applyToEither→join(),异步任务像流水线一样串联。 - 3 个红线:必须自定义线程池 / 必须
exceptionally兜底 / 必须区分thenApply与thenCompose------踩任意一个就上线翻车。
tags :#java #并发 #CompletableFuture #异步编程 #Future #链式编排 #JDK17 #源码解析