目录
- [一、为什么需要 CompletableFuture](#一、为什么需要 CompletableFuture)
- [二、创建 CompletableFuture](#二、创建 CompletableFuture)
- 三、获取执行结果
- 四、任务完成后的回调
- 五、任务编排
-
- 1、thenApply
- 2、thenCompose
- [3、thenApply 与 thenCompose 的区别](#3、thenApply 与 thenCompose 的区别)
- 六、多任务并发执行
- 七、异常处理
- 八、重点区别
-
- [Future 和 CompletableFuture 的区别](#Future 和 CompletableFuture 的区别)
- [thenApply 和 thenCompose 区别](#thenApply 和 thenCompose 区别)
- [allOf 和 anyOf 区别](#allOf 和 anyOf 区别)
- 为什么生产环境要使用自定义线程池
- 九、项目实战:批量推送订单到财务系统
-
- 实战背景
- 第一步:配置线程池
- 第二步:编写业务代码
- 第三步:理解代码执行流程
- [第四步:allOf 在这里做了什么](#第四步:allOf 在这里做了什么)
一、为什么需要 CompletableFuture
在 Java 并发编程中,最早我们通常使用 Thread、Runnable、Callable 和 Future 来执行异步任务。
例如:
java
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "Hello";
});
String result = future.get();
虽然可以实现异步执行,但存在明显缺点:
get()会阻塞当前线程- 无法方便地进行任务编排
- 无法链式调用
- 多任务组合比较麻烦
- 异常处理不够优雅
因此 JDK8 引入了 CompletableFuture。
它不仅能够表示一个异步任务,还支持:
- 异步回调
- 链式调用
- 多任务编排
- 异常处理
- 并发任务组合
在 Spring Boot、微服务、Feign 调用、批量处理等场景中使用非常广泛。
二、创建 CompletableFuture
1、runAsync(无返回值)
执行一个异步任务,但不返回结果。
java
CompletableFuture.runAsync(() -> {
System.out.println("执行异步任务");
});
源码:
java
public static CompletableFuture<Void> runAsync(Runnable runnable)
返回值:
java
CompletableFuture<Void>
2、supplyAsync(有返回值)
执行异步任务并返回结果。
java
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> {
return "Hello";
});
获取结果:
java
String result = future.join();
输出:
text
Hello
3、指定线程池
生产环境不建议直接使用:
java
CompletableFuture.runAsync(...)
因为默认使用:
java
ForkJoinPool.commonPool()
推荐使用业务线程池:
java
ExecutorService executor =
Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, executor);
三、获取执行结果
1、get()
java
String result = future.get();
特点:
- 阻塞等待
- 需要处理检查异常
java
InterruptedException
ExecutionException
2、join()
java
String result = future.join();
特点:
- 阻塞等待
- 不需要处理检查异常
项目中使用更频繁。
3、getNow()
立即返回结果。
java
String result = future.getNow("默认值");
如果任务未完成:
text
返回默认值
四、任务完成后的回调
1、thenAccept
消费结果但不返回值。
java
CompletableFuture.supplyAsync(() -> "hello")
.thenAccept(System.out::println);
输出:
text
hello
2、thenRun
不关心前面任务结果。
java
CompletableFuture.supplyAsync(() -> "hello")
.thenRun(() -> {
System.out.println("执行完成");
});
3、thenApply
对结果进行转换。
java
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(String::toUpperCase);
System.out.println(future.join());
输出:
text
HELLO
执行流程:
text
hello
↓
thenApply
↓
HELLO
五、任务编排
1、thenApply
用于结果转换。
java
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> 10)
.thenApply(i -> i * 2)
.thenApply(i -> i + 5);
System.out.println(future.join());
结果:
text
25
流程:
text
10
↓
20
↓
25
2、thenCompose
用于依赖任务。
例如:
先查用户,再查订单。
java
CompletableFuture<Order> future =
CompletableFuture.supplyAsync(() -> queryUser(1L))
.thenCompose(user ->
CompletableFuture.supplyAsync(
() -> queryOrder(user.getId())
));
流程:
text
查询用户
↓
获取用户ID
↓
查询订单
3、thenApply 与 thenCompose 的区别
错误写法:
java
CompletableFuture<CompletableFuture<Order>>
.thenApply(user ->
CompletableFuture.supplyAsync(
() -> queryOrder(user.getId())
))
得到的是嵌套 Future。
正确写法:
java
.thenCompose(...)
结果:
java
CompletableFuture<Order>
六、多任务并发执行
这是实际项目最常见的场景。
例如订单详情页面:
需要同时查询:
- 用户信息
- 订单信息
- 发票信息
如果顺序执行:
text
1秒 + 1秒 + 1秒 = 3秒
如果并发执行:
text
max(1秒,1秒,1秒)=1秒
1、allOf
等待全部任务完成。
java
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> queryUser());
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(() -> queryOrder());
CompletableFuture<Invoice> invoiceFuture =
CompletableFuture.supplyAsync(() -> queryInvoice());
CompletableFuture.allOf(
userFuture,
orderFuture,
invoiceFuture
).join();
此时:
text
三个任务全部完成
↓
继续执行
2、anyOf
任意一个完成即可返回。
java
CompletableFuture<Object> future =
CompletableFuture.anyOf(
future1,
future2,
future3
);
适用于:
- 多缓存节点
- 多数据源
- 多搜索服务
谁先返回用谁。
七、异常处理
1、exceptionally
发生异常时返回默认值。
java
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> {
int i = 1 / 0;
return 100;
})
.exceptionally(ex -> -1);
System.out.println(future.join());
结果:
text
-1
2、handle
无论成功还是失败都会执行。
java
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> 100)
.handle((result, ex) -> {
if (ex != null) {
return -1;
}
return result * 2;
});
结果:
text
200
3、whenComplete
常用于日志记录。
java
CompletableFuture.supplyAsync(() -> 100)
.whenComplete((result, ex) -> {
System.out.println(result);
if (ex != null) {
ex.printStackTrace();
}
});
八、重点区别
Future 和 CompletableFuture 的区别
| Future | CompletableFuture |
|---|---|
| 只能获取结果 | 支持回调 |
| 不支持编排 | 支持链式编排 |
| 不支持组合 | 支持 allOf |
| 功能简单 | 功能强大 |
thenApply 和 thenCompose 区别
thenApply:
text
A → B
结果转换。
thenCompose:
text
A → CompletableFuture<B>
自动拍平 Future。
allOf 和 anyOf 区别
allOf:
text
全部完成继续
anyOf:
text
任意完成继续
为什么生产环境要使用自定义线程池
避免所有业务共享:
java
ForkJoinPool.commonPool()
导致:
- 线程耗尽
- 请求堆积
- 性能抖动
推荐:
java
CompletableFuture.runAsync(
task,
customExecutor
);
九、项目实战:批量推送订单到财务系统
实战背景
假设系统需要向财务系统推送 100 个订单。
每个订单调用一次远程接口:
text
推送订单1
推送订单2
推送订单3
...
推送订单100
每次推送耗时约 1 秒。
如果串行执行:
text
100 × 1秒 = 100秒
用户需要等待很久。
因此我们希望:
text
100个订单同时推送
利用线程池并发处理。
第一步:配置线程池
Spring Boot 配置:
java
@Configuration
public class AsyncThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor asyncThreadPool() {
ThreadPoolTaskExecutor executor =
new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(5);
// 最大线程数
executor.setMaxPoolSize(10);
// 队列容量
executor.setQueueCapacity(100);
// 线程名前缀
executor.setThreadNamePrefix("push-order-");
// 优雅关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
第二步:编写业务代码
可以直接复制运行观察效果。
java
@Service
public class PushOrderService {
@Autowired
private ThreadPoolTaskExecutor asyncThreadPool;
public void pushOrders() {
// 模拟100个订单
List<String> orders = IntStream.rangeClosed(1, 100)
.mapToObj(i -> "ORDER-" + i)
.collect(Collectors.toList());
// 用于保存所有异步任务
List<CompletableFuture<Void>> futures = new ArrayList<>();
long start = System.currentTimeMillis();
//通过循环 把100个订单都放进线程池 ,然后放到 futures 里
for (String orderNo : orders) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 开始推送:" + orderNo);
// 模拟远程调用耗时
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 推送成功:" + orderNo);
} catch (Exception e) {
e.printStackTrace();
}
}, asyncThreadPool);
futures.add(future);
}
//把所有Future合并成一个Future,然后等待全部订单推送完成
CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
).join();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) + " ms");
}
}
第三步:理解代码执行流程
假设线程池:
text
核心线程数:5
订单:
text
ORDER-1
ORDER-2
...
ORDER-100
执行流程:
text
线程1 -> ORDER-1
线程2 -> ORDER-2
线程3 -> ORDER-3
线程4 -> ORDER-4
线程5 -> ORDER-5
第一批完成:
text
线程1 -> ORDER-6
线程2 -> ORDER-7
...
持续执行。
第四步:allOf 在这里做了什么
核心代码:
java
CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
).join();
作用:
text
等待所有订单推送完成
流程:
text
future1 ────────┐
future2 ────────┤
future3 ────────┤
future4 ────────┤
future5 ────────┘
↓
allOf
↓
join
↓
继续执行
如果少了这段代码:
java
CompletableFuture.runAsync(...)
提交后方法会立刻结束。
此时:
text
订单还没推送完
方法已经返回
而有了:
java
allOf(...).join()
系统会等待所有订单推送成功后再继续执行后面的逻辑。
上面的 CompletableFuture 实战在企业项目中也常使用于以下场景:
text
批量推送
批量导入
批量同步
批量调用Feign
批量调用第三方接口