CompletableFuture 异步编排
面试频率:★★★★☆
工作频率:★★★★★
⚡30 秒速记(复习只看这里)
🟢 一句话
CompletableFuture 是 Java 提供的异步编程工具,可以实现异步执行、任务串联、多个任务并行和结果合并。
核心:
异步执行
+
任务回调
+
任务组合
🟡 最常用的方法
runAsync
↓
异步执行,无返回值
supplyAsync
↓
异步执行,有返回值
thenApply
↓
拿上一步结果继续处理
allOf
↓
等待多个任务全部完成
join
↓
获取异步结果
🔴 最重要的一点
生产环境:
不建议直接:
CompletableFuture.supplyAsync(() -> query());
更推荐:
CompletableFuture.supplyAsync(
() -> query(),
executor
);
也就是:
使用自己配置的线程池,避免大量异步任务占用公共线程池。
📚 一、CompletableFuture 是什么?
CompletableFuture:
Java 8 提供的异步编程工具。
包:
java.util.concurrent.CompletableFuture
主要解决:
异步执行
↓
任务编排
↓
结果合并
二、为什么需要 CompletableFuture?
假设:
订单详情接口需要查询:
订单信息
用户信息
物流信息
如果串行:
查询订单 100ms
↓
查询用户 200ms
↓
查询物流 300ms
总时间大约:
600ms
如果三个任务互相不依赖:
可以并行:
┌→ 查询订单 100ms
请求 ───┼→ 查询用户 200ms
└→ 查询物流 300ms
最终耗时大约:
300ms
而不是:
600ms
这就是 CompletableFuture 非常常见的使用场景。
三、Future 有什么问题?
以前可以:
Future<String> future =
executor.submit(() -> queryUser());
获取结果:
String user = future.get();
问题:
future.get()
↓
当前线程阻塞
↓
等待异步任务完成
而且:
Future 对多个任务之间的组合、串联操作支持比较弱。
CompletableFuture 可以:
任务A完成
↓
自动执行任务B
↓
任务B完成
↓
处理结果
更适合复杂异步流程。
四、runAsync ⭐⭐⭐⭐
没有返回值
例如:
CompletableFuture.runAsync(() -> {
sendEmail();
});
适合:
发送邮件
发送短信
记录日志
这些:
不关心返回结果的任务。
完整:
CompletableFuture<Void> future =
CompletableFuture.runAsync(() -> {
sendEmail();
});
返回:
CompletableFuture<Void>
五、supplyAsync ⭐⭐⭐⭐⭐
有返回值
例如:
CompletableFuture<User> future =
CompletableFuture.supplyAsync(() -> {
return queryUser();
});
适合:
查询数据库
调用接口
查询Redis
获取业务数据
例如:
User user = future.join();
获得结果。
六、runAsync 和 supplyAsync 区别
| 方法 | 返回值 | 使用场景 |
|---|---|---|
| runAsync | 没有 | 发短信、写日志 |
| supplyAsync | 有 | 查询数据库、调用接口 |
记:
run
↓
只执行
supply
↓
执行 + 返回数据
七、thenApply:任务串联 ⭐⭐⭐⭐⭐
假设:
第一步:
查询用户
第二步:
根据用户查询订单
第二步依赖第一步。
可以:
CompletableFuture
.supplyAsync(() -> queryUser())
.thenApply(user -> {
return queryOrder(user);
});
流程:
queryUser()
↓
得到 User
↓
thenApply
↓
queryOrder(user)
↓
得到 Order
所以:
thenApply 可以拿到上一步的返回结果,然后继续处理,并返回新的结果。
八、thenAccept
如果:
需要上一步结果。
但是:
自己不需要返回结果。
例如:
CompletableFuture
.supplyAsync(() -> queryUser())
.thenAccept(user -> {
sendEmail(user);
});
流程:
查询User
↓
拿到User
↓
发送邮件
↓
结束
区别:
thenApply
有返回值
thenAccept
没有返回值
九、thenRun
如果:
既不关心上一步结果。
也没有返回值。
例如:
CompletableFuture
.supplyAsync(() -> queryUser())
.thenRun(() -> {
System.out.println("执行完成");
});
三者区别:
thenApply
拿结果
+
有返回值
thenAccept
拿结果
+
无返回值
thenRun
不拿结果
+
无返回值
十、多个任务并行 ⭐⭐⭐⭐⭐
例如:
订单详情接口:
需要:
订单
用户
物流
可以:
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(() -> {
return queryOrder();
});
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> {
return queryUser();
});
CompletableFuture<Logistics> logisticsFuture =
CompletableFuture.supplyAsync(() -> {
return queryLogistics();
});
三个任务:
同时执行
而不是:
一个执行完
再执行下一个
十一、allOf ⭐⭐⭐⭐⭐
如果需要:
等待所有任务完成。
使用:
CompletableFuture.allOf(
orderFuture,
userFuture,
logisticsFuture
).join();
流程:
订单任务 ─────┐
用户任务 ─────┼→ allOf → 全部完成
物流任务 ─────┘
注意:
allOf() 本身:
不会直接把三个任务结果组合起来。
所以通常:
CompletableFuture.allOf(
orderFuture,
userFuture,
logisticsFuture
).join();
Order order = orderFuture.join();
User user = userFuture.join();
Logistics logistics = logisticsFuture.join();
十二、anyOf
和 allOf 不同。
allOf
所有任务完成
才结束。
anyOf
任意一个任务完成
就结束。
例如:
CompletableFuture.anyOf(
future1,
future2,
future3
).join();
十三、join 和 get 区别 ⭐⭐⭐⭐
两个都可以:
获取异步结果。
get()
future.get();
需要处理:
检查型异常。
例如:
try {
User user = future.get();
} catch (Exception e) {
}
join()
User user = future.join();
不要求强制捕获检查型异常。
代码更简洁。
所以业务代码:
经常看到:
join()
十四、CompletableFuture 使用哪个线程池? ⭐⭐⭐⭐⭐
如果:
CompletableFuture.supplyAsync(() -> queryUser());
没有指定线程池。
默认使用:
ForkJoinPool.commonPool()
也就是:
JVM 公共线程池。
问题:
如果项目中很多地方:
都使用公共线程池。
例如:
业务A
业务B
业务C
业务D
全部:
ForkJoinPool.commonPool
一个业务任务特别慢:
可能影响其他业务。
所以生产环境推荐:
指定自己的线程池。
十五、配合 ThreadPoolTaskExecutor ⭐⭐⭐⭐⭐
你昨天刚学:
ThreadPoolTaskExecutor
Spring项目可以配置:
@Bean
public ThreadPoolTaskExecutor orderExecutor() {
ThreadPoolTaskExecutor executor =
new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("order-");
executor.initialize();
return executor;
}
使用:
CompletableFuture<User> future =
CompletableFuture.supplyAsync(
() -> queryUser(),
orderExecutor
);
这样:
CompletableFuture
↓
ThreadPoolTaskExecutor
↓
ThreadPoolExecutor
↓
线程执行任务
十六、为什么推荐自己配置线程池?
因为可以控制:
核心线程数
最大线程数
队列容量
拒绝策略
线程名称
例如:
订单业务
↓
order-thread-pool
短信业务
↓
sms-thread-pool
互相隔离。
如果短信服务卡死:
不会把订单线程池全部占满。
这叫:
线程池隔离。
十七、异常处理 ⭐⭐⭐⭐
异步任务:
很容易出现一个问题:
线程里面异常了
↓
主线程不知道
CompletableFuture 可以:
CompletableFuture
.supplyAsync(() -> queryUser())
.exceptionally(e -> {
log.error("查询用户失败", e);
return null;
});
流程:
正常
↓
返回User
异常
↓
exceptionally
↓
返回兜底结果
还可以:
.handle((result, exception) -> {
if (exception != null) {
return null;
}
return result;
});
十八、工作场景:订单详情接口
例如接口:
GET /order/detail
需要:
订单信息
用户信息
物流信息
优惠券信息
这些任务:
互相不依赖。
可以并行:
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(
() -> queryOrder(),
executor
);
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(
() -> queryUser(),
executor
);
CompletableFuture<Logistics> logisticsFuture =
CompletableFuture.supplyAsync(
() -> queryLogistics(),
executor
);
CompletableFuture.allOf(
orderFuture,
userFuture,
logisticsFuture
).join();
最后:
Order order = orderFuture.join();
User user = userFuture.join();
Logistics logistics = logisticsFuture.join();
组装:
OrderDetailVO vo = new OrderDetailVO();
vo.setOrder(order);
vo.setUser(user);
vo.setLogistics(logistics);
返回前端。
十九、什么时候不适合 CompletableFuture?
如果任务本身:
必须严格按照顺序执行。
例如:
创建订单
↓
拿到订单ID
↓
扣库存
↓
保存支付记录
后面的任务:
依赖前面的结果。
这种不能简单全部并行。
另外:
非常简单的业务:
没必要为了异步而异步。
否则:
代码复杂
异常处理复杂
线程池管理复杂
二十、CompletableFuture 和 @Async 区别
@Async
Spring提供:
@Async
public void sendEmail() {
}
特点:
简单。
适合:
单个方法异步
CompletableFuture
Java提供。
适合:
多个异步任务
↓
任务组合
↓
结果合并
简单理解:
@Async
↓
我要让这个方法异步
CompletableFuture
↓
我要编排多个异步任务
💼 工作场景总结
适合:
一个接口需要查询多个互不依赖的数据
例如:
商品详情:
商品信息
库存
评价
推荐商品
优惠券
可以:
┌→ 商品
│
请求 ──┼→ 库存
│
├→ 评价
│
└→ 优惠券
↓
allOf
↓
组装结果
↓
返回
减少接口响应时间。
🎤 面试回答(30秒)
CompletableFuture 是 Java 8 提供的异步编程工具,可以实现异步任务执行、任务串联、多个任务并行以及结果合并。runAsync 用于没有返回值的任务,supplyAsync 用于有返回值的任务,thenApply 可以串联任务,allOf 可以等待多个任务全部完成。CompletableFuture 默认使用 ForkJoinPool.commonPool,生产环境通常建议指定自己的线程池,避免不同业务之间相互影响。
🎯 面试追问
Q1:CompletableFuture 是干什么的?
答:
异步执行和异步任务编排。
Q2:runAsync 和 supplyAsync 区别?
答:
runAsync
无返回值
supplyAsync
有返回值
Q3:thenApply 是什么?
答:
拿到上一个任务的结果,继续执行下一个任务,并返回新的结果。
Q4:allOf 是什么?
答:
等待多个 CompletableFuture 全部执行完成。
Q5:allOf 会返回所有任务结果吗?
答:
不会。
allOf 主要负责等待全部完成。
具体结果:
需要分别通过:
future.join();
获取。
Q6:join 和 get 区别?
答:
都可以获取结果。
get 需要处理检查型异常。
join 不要求强制捕获检查型异常。
Q7:CompletableFuture 默认使用什么线程池?
答:
ForkJoinPool.commonPool()
Q8:为什么生产环境推荐自己配置线程池?
答:
避免所有异步任务共用公共线程池,造成业务互相影响,同时可以控制核心线程数、最大线程数、队列和拒绝策略。
Q9:CompletableFuture 和 @Async 区别?
答:
@Async 适合简单的方法异步。
CompletableFuture 更适合多个异步任务的编排和结果合并。
⚠️ 工作踩坑
1. 不指定线程池
CompletableFuture.supplyAsync(() -> query());
可能:
所有业务共用:
ForkJoinPool.commonPool
生产环境最好:
指定业务线程池。
2. 忘记处理异常
异步线程发生异常:
可能不容易发现。
需要:
exceptionally()
或者:
handle()
处理。
3. 所有任务都使用 CompletableFuture
错误。
如果任务之间:
有严格依赖关系。
不能为了并行强行并行。
4. 线程池配置过大
CompletableFuture 并不是:
线程越多越快
线程太多:
上下文切换增加
↓
CPU压力增加
❓ 自测
-
CompletableFuture 是什么?
-
为什么需要 CompletableFuture?
-
runAsync 和 supplyAsync 有什么区别?
-
thenApply 是干什么的?
-
thenAccept 和 thenRun 有什么区别?
-
allOf 是什么?
-
allOf 会直接返回所有任务结果吗?
-
join 和 get 有什么区别?
-
CompletableFuture 默认使用什么线程池?
-
为什么生产环境推荐指定自己的线程池?
-
CompletableFuture 和 ThreadPoolTaskExecutor 是什么关系?
-
CompletableFuture 和 @Async 有什么区别?