CompletableFuture
# 一、是什么
- 定位:Java 异步编程工具类,同时实现
Future、CompletionStage两个接口,是对原生Future的增强。 - 原生 Future 的痛点
- 只能通过
get()阻塞获取结果,无回调机制; - 无法链式编排多段异步逻辑;
- 不能便捷组合多个异步任务(并行、等待任意 / 全部完成);
- 异常处理繁琐,只能阻塞时捕获。
- 只能通过
- CompletableFuture 核心能力
- 支持非阻塞回调,任务完成自动执行后续逻辑,无需阻塞 / 轮询;
- 流式链式调用,串行处理异步结果,解决回调嵌套;
- 内置多任务组合 API:
allOf、anyOf、thenCombine; - 完善异步异常捕获:
exceptionally、handle; - 可手动控制任务完成:
complete()、completeExceptionally(),适配回调、RPC、MQ 场景; - 区分同步 / 异步回调(
thenXXX、thenXXXAsync),可自定义业务线程池隔离。
# 二、JDK 引入版本
- JDK 8 首次引入,作为 Lambda、Stream 配套的异步编程工具;
- JDK9 / JDK11 / JDK17 持续小幅增强 API,底层无架构改动,核心用法完全兼容 JDK8。
# 三、核心创建方式
CompletableFuture.runAsync(Runnable):无返回值异步任务CompletableFuture.supplyAsync(Supplier):有返回值异步任务
不传第二个参数
Executor,默认使用ForkJoinPool.commonPool();IO 密集业务建议传入自定义线程池,避免共用公共池耗尽。
# 四、常用分类 API
# 1. 串行处理(上一步结果作为入参)
thenApply:接收结果,转换并返回新值thenAccept:消费结果,无返回值thenRun:不依赖上一步结果,仅等待完成执行动作- 带
Async后缀:后续阶段重新提交线程池异步执行
# 2. 异常处理
exceptionally(ex -> 兜底值):异常时返回备用数据handle((res, ex) -> {}):成功、异常都会执行,同时拿到结果与异常
# 3. 多任务组合
thenCombine(cf, (r1,r2) -> merge):两个任务全部完成,合并两个结果CompletableFuture.allOf(cf1,cf2):等待所有任务执行完毕,无返回值CompletableFuture.anyOf(cf1,cf2):任意一个任务完成,立刻执行回调
# 五、获取结果两种模式(重点区分阻塞 / 非阻塞)
阻塞获取(主动等待)
get():抛出受检异常,需 try-catchjoin():抛出运行时异常,无需捕获,业务更常用
适用场景:同步 Web 接口需要组装结果返回,多任务并行缩短整体耗时。
非阻塞回调(推荐异步场景)
全程不调用
get/join,通过链式回调自动执行,主线程无阻塞。
# 六、与 Executor + Callable + 原生 Future 核心区别
Executor.submit+Callable只是基础异步执行工具,仅能阻塞式获取结果;CompletableFuture在其基础上增加了异步回调、链式编排、多任务聚合、手动完成、异步异常捕获,是一套完整的异步流程编程模型
# 七、开发注意事项
- IO 密集场景不要直接使用默认
ForkJoinPool,自定义线程池隔离; - 异步链路必须捕获异常,否则异常只会在调用
get/join时抛出,容易静默丢失; allOf无返回值,如需结果需在回调内手动调用join();- 大量并发异步任务优先使用非阻塞回调,减少主线程阻塞耗时。
# 八、上代码
# Executor+Future
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<String> future = pool.submit(() -> "数据");
// 必须阻塞,卡住当前线程,直到任务完成
String res = future.get();
System.out.println(res);
Future<String> f1 = pool.submit(task1);
Future<String> f2 = pool.submit(task2);
// 两处阻塞
String r1 = f1.get();
String r2 = f2.get();
String merge = r1 + r2;
痛点:主线程阻塞;如果要做后续处理,必须等 get 返回,无法异步回调
# CompletableFuture
执行完成回调:
CompletableFuture.supplyAsync(() -> "数据")
.thenApply(s -> s + "后缀") // 任务完成自动执行,不阻塞主线程
.thenAccept(System.out::println);
// 主线程可以继续干别的,不用等待
合并任务,都完成后才执行回调anyOf:
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(task1);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(task2);
// 非阻塞自动合并
cf1.thenCombine(cf2, (a,b) -> a+b).thenAccept(System.out::println);
合并任务,任意一个任务完成就执行回调 anyOf:
CompletableFuture<Object> any = CompletableFuture.anyOf(cf1, cf2);
any.thenAccept(res -> System.out.println("任意任务完成:" + res));
# 九、疑问
# 关键区分:阻塞发生在哪
主线程直接调用 cf.join () /cf.get ()→ 主线程阻塞,卡住后续代码,和原始 Future 无差别;
在 thenAccept /thenApply/thenCombine 等回调内部调用 join ()→ 阻塞的是回调执行线程
(线程池工作线程),主线程早已释放,不会影响主流程;
全程只用链式回调,不写任何 get/join→ 全程无阻塞,纯异步事件驱动。
# Web接口阻塞场景
Web 接口(SpringMVC/SpringBoot)必须同步返回数据给前端,响应流程是同步模型:
@GetMapping("/query")
public String query() {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(task1);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(task2);
// 接口要同步返回,只能阻塞等待所有异步任务结果
return cf1.join() + cf2.join();
}
这种场景阻塞不可避免,但异步并行查询缩短整体耗时:
串行执行:耗时 = task1 耗时 + task2 耗时
CompletableFuture 并行:耗时 = max (task1 耗时,task2 耗时)
阻塞只是等最慢的那个任务,整体性能大幅提升。
上次更新: 2026-06-10 17:28:52