CompletableFuture 详细用法讲解
1. CompletableFuture 是什么?来自哪里?有什么用?
CompletableFuture
是 Java 8 引入的一个强大的异步编程工具,位于 java.util.concurrent
包中。它是对传统 Future
接口的扩展,旨在提供更灵活、更强大的异步任务处理能力。
作用
- 异步执行:允许任务在后台线程中运行,主线程无需等待即可继续执行。
- 结果处理:提供丰富的 API 来处理异步任务的结果(如成功或失败时的回调)。
- 任务组合:支持将多个异步任务组合起来,处理复杂的依赖关系。
- 异常处理:内置机制来捕获和处理异步任务中的异常。
与传统的 Future
相比,CompletableFuture
解决了 Future
的几个痛点:
Future
无法主动完成,只能被动等待get()
结果,阻塞线程。Future
不支持回调机制,无法方便地处理结果或异常。Future
无法轻松组合多个任务。
CompletableFuture
的出现填补了这些空白,使 Java 的异步编程更加现代化和函数式。
2. 如何使用 CompletableFuture?它解决了什么问题?
使用 CompletableFuture
,你可以通过静态方法(如 supplyAsync
、runAsync
)启动异步任务,并通过链式调用(如 thenApply
、thenAccept
、exceptionally
等)处理结果或异常。
基本用法示例
java
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
// 异步计算一个值
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, CompletableFuture!";
});
// 当任务完成时打印结果
future.thenAccept(System.out::println);
System.out.println("主线程继续执行...");
}
}
输出:
erlang
主线程继续执行...
Hello, CompletableFuture!(1秒后)
解决的问题
- 阻塞问题 :传统
Future.get()
会阻塞线程,而CompletableFuture
提供非阻塞的回调方式。 - 回调地狱:通过链式调用替代嵌套回调,代码更简洁。
- 组合困难 :支持多任务并行执行并等待所有任务完成(如
allOf
)。
3. 结合互联网场景:异步查询简历信息
在互联网应用中,异步处理非常常见。例如,一个招聘网站需要从数据库中查询某份简历的获奖经历(RewardExp
)、资格证书(Credential
)和工作种类(ResumeJobs
)。这些查询是独立的,可以并行执行以提高性能。
以下是使用 CompletableFuture
的实现:
java
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ResumeService {
public void fetchResumeDetails(Resume resume) {
// 查询获奖经历
LambdaQueryWrapper<RewardExp> rewardExpQuery = new LambdaQueryWrapper<RewardExp>()
.eq(RewardExp::getResumeId, resume.getId())
.eq(RewardExp::getDelFlag, NORMAL)
.orderByDesc(RewardExp::getDate);
CompletableFuture<List<RewardExp>> rewardExpFuture = CompletableFuture.supplyAsync(() ->
rewardExpMapper.selectList(rewardExpQuery)
);
// 查询资格证书
LambdaQueryWrapper<Credential> credentialQuery = new LambdaQueryWrapper<Credential>()
.eq(Credential::getResumeId, resume.getId())
.eq(Credential::getDelFlag, NORMAL)
.orderByDesc(Credential::getDate);
CompletableFuture<List<Credential>> credentialFuture = CompletableFuture.supplyAsync(() ->
credentialMapper.selectList(credentialQuery)
);
// 查询工种
LambdaQueryWrapper<ResumeJobs> jobsQuery = new LambdaQueryWrapper<ResumeJobs>()
.eq(ResumeJobs::getResumeId, resume.getId())
.eq(ResumeJobs::getDelFlag, NORMAL);
CompletableFuture<List<ResumeJobs>> jobsFuture = CompletableFuture.supplyAsync(() ->
resumeJobsMapper.selectList(jobsQuery)
);
// 等待所有任务完成
CompletableFuture.allOf(rewardExpFuture, credentialFuture, jobsFuture).join();
// 获取结果
List<RewardExp> rewards = rewardExpFuture.getNow(null);
List<Credential> credentials = credentialFuture.getNow(null);
List<ResumeJobs> jobs = jobsFuture.getNow(null);
// 处理结果(例如返回给前端)
System.out.println("获奖经历: " + rewards);
System.out.println("资格证书: " + credentials);
System.out.println("工种: " + jobs);
}
}
效果
- 并行执行:三个查询同时进行,总耗时接近于最慢的单个查询时间,而不是串行执行的总和。
- 非阻塞 :主线程可以继续处理其他逻辑,直到需要结果时才调用
join()
或getNow()
。 - 场景适用性:这种方式在高并发场景(如 REST API)中能显著提升响应速度。
4. CompletableFuture 的原理、依赖和失效场景
原理
CompletableFuture
基于 Java 的 ForkJoinPool
(默认线程池)执行异步任务。它通过以下机制工作:
- 任务提交 :
supplyAsync
或runAsync
将任务提交到线程池。 - 完成通知 :任务完成后,结果或异常存储在
CompletableFuture
对象中。 - 链式处理 :通过内部的回调链表(
Completion
对象),在任务完成时触发后续操作(如thenApply
)。 - 事件驱动 :依赖 Java 的并发工具(如
LockSupport
)实现高效的线程协调。
依赖
- 线程池 :默认使用
ForkJoinPool.commonPool()
,也可以通过自定义Executor
指定线程池。 - Java 并发包 :依赖
java.util.concurrent
中的工具类。
失效场景
- 线程池耗尽 :如果线程池资源不足(例如任务过多或线程被长时间占用),新任务会被阻塞或拒绝。
- 解决办法:自定义线程池并监控其状态。
- 任务阻塞 :如果异步任务中有同步 I/O 或无限循环,线程会被占用,导致性能下降。
- 解决办法:确保任务适合异步执行,避免阻塞操作。
- 异常未处理 :若未使用
exceptionally
或类似方法捕获异常,任务失败可能无声无息。- 解决办法:始终添加异常处理逻辑。
- 不适合短任务 :对于非常短暂的任务,异步调用的开销可能超过收益。
- 解决办法:评估任务耗时,短任务直接同步执行。
总结
CompletableFuture
是 Java 异步编程的利器,解决了传统 Future
的局限性。通过链式调用和任务组合,它让代码更优雅、高效。在互联网场景中,如并行查询数据库,它能显著提升性能。理解其原理和局限性,可以帮助开发者在合适场景下发挥其最大价值。