--- title: "06-CompletableFuture异步编程" created: 2026-09-03 tags: - Java --- # CompletableFuture异步编程 > [!note] 本篇定位 > 线程池能"并行",但多个异步任务之间有依赖、要合并、要异常兜底时,用 Future+get() 手写编排是灾难。`CompletableFuture`(JDK 8)把异步任务变成一条**声明式流水线**——链式串联、并行合并、任选其一、异常兜底,全都能写出来。本篇前置:[[03-线程池|03-线程池]] 与 [[07-Lambda与函数式接口|07-Lambda与函数式接口]]。 ## 一、问题提出:老 Future 的三个痛点 ```java ExecutorService pool = Executors.newFixedThreadPool(4); Future f1 = pool.submit(() -> queryOrder(id)); Future f2 = pool.submit(() -> queryUser(uid)); Order order = f1.get(); // 1. get() 阻塞主线程,等的时间不能干别的 User user = f2.get(); // 2. 多个 Future 只能逐个 get,无法"两个都好了再做" // 3. 任务之间要串行(拿到订单再查用户),只能自己 sleep + 循环轮询 ``` `CompletableFuture` 提供 `~Async` 系列方法,把"等"也交给框架。 ## 二、创建:四把钥匙 ```java // 1. 无返回值的动作 CompletableFuture f = CompletableFuture.runAsync(() -> sendMsg(user)); // 2. 有返回值 CompletableFuture f2 = CompletableFuture.supplyAsync(() -> queryOrder(id)); // 3. 指定线程池(生产必加,别用默认的 ForkJoin 公共池) CompletableFuture.supplyAsync(() -> queryUser(uid), bizPool); // 4. 手动完成(回调/外部系统触发的适配层) CompletableFuture manual = new CompletableFuture<>(); manual.complete("done"); // 手动塞结果 manual.completeExceptionally(e); // 手动塞异常 ``` **默认线程池要警惕**:不加参数的 `runAsync/supplyAsync` 跑在 `ForkJoinPool.commonPool()` 上,它是全局共享、并行度 = CPU 核数-1——**IO 密集任务必须显式传自己的线程池**,否则可能拖垮整个 JVM 的公共池。 ## 三、编排:单线串行(then*) ```java supplyAsync(() -> queryOrder(id)) // 查订单 .thenApply(order -> order.getAmount()) // 同步转换(同一线程) .thenApplyAsync(amount -> calcTax(amount), pool) // 异步转换(换线程) .thenAccept(tax -> System.out.println(tax)) // 消费结果,不返回 .thenRun(() -> System.out.println("done")); // 只要前一步完成就执行 ``` 方法名口诀:`thenApply`=变换、`thenAccept`=消费、`thenRun`=只跑动作;带 `Async` 后缀 = 换线程执行,可指定线程池。 ## 四、编排:多线合并与竞赛 ```java // AND:两个都完成再做(合并两路结果) CompletableFuture userName = supplyAsync(() -> queryUser(uid), pool); CompletableFuture order = supplyAsync(() -> queryOrder(id), pool); userName.thenCombine(order, (u, o) -> String.format("%s 的订单 %s", u, o.getId())) .thenAccept(System.out::println); // ALL / ANY:等全部 / 等任意一个 CompletableFuture.allOf(userName, order).join(); // 全部完成 CompletableFuture.anyOf(userName, order).join(); // 任一完成即返回(返回 Object) // 竞速:两个路径谁先到用谁 supplyAsync(this::dbQuery).applyToEither( supplyAsync(this::cacheQuery), r -> r); ``` ## 五、异常兜底:异步世界里的 try-catch ```java supplyAsync(() -> queryOrder(id)) .exceptionally(ex -> { log.error("查询失败,回退", ex); return Order.DEFAULT; }) // 异常→默认值 .handle((order, ex) -> ex != null ? fallback(order) : enrich(order)); // 正常和异常都处理 .whenComplete((order, ex) -> { // 无论成败的收尾动作 if (ex != null) log.error("失败", ex); else metrics.record(order); }); ``` **锅在哪烧就在哪接**:`exceptionally/handle` 只处理**自己之前那一段**的异常,不能隔好几个 then 再统一 catch——每个可能出错的环节就近兜底。 ## 六、三大易错点 1. **get()/join() 阻塞**:`get()` 抛受检异常(InterruptedException),`join()` 抛非受检(CompletionException)——两者都会阻塞线程,链式写法里尽量用回调,最后才 join 一次收口; 2. **默认线程池**:见第二节,IO 任务必须显式指定业务线程池; 3. **thenApply vs thenApplyAsync** 的坑:`thenApply` 在上一步完成线程上同步执行,任务很重会占住上一个工作线程——重的转换用 `thenApplyAsync` 换到线程池。判断标准:**当前步骤是否耗 CPU / 是否想换线程**。 ## 七、高频面试题速答 1. **CompletableFuture 和 Future 区别?** Future 只能 get 阻塞等结果;CompletableFuture 支持链式回调(then*)、多路合并(allOf/anyOf/combine)、异常兜底(exceptionally/handle)。 2. **thenApply 和 thenAccept 区别?** thenApply 返回新结果(变换),thenAccept 只消费不返回。 3. **默认线程池是什么?为什么不能用来跑 IO?** ForkJoinPool.commonPool,全局共享、并行度 CPU 核数-1,IO 任务占满会饿死其他使用方——显式传线程池。 4. **allOf 和 anyOf 返回值是什么?** allOf 返回 `CompletableFuture`(需要再 thenApply 取各结果),anyOf 返回第一个完成的结果(Object,要强转)。真正并行的底层是 [[03-线程池|03-线程池]]。 ## 勾连 - 线程池参数与拒绝策略:[[03-线程池|03-线程池]] - 底层 Future/FutureTask 与 Callable:[[01-线程基础与生命周期|01-线程基础与生命周期]] - 编排思想的流式镜像:[[08-Stream与Optional|08-Stream与Optional]] --- ⬅️ [[05-并发容器与工具类|05-并发容器与工具类]] 🏠 [[00-Java|00-Java]] ➡️ [[07-并发高频面试题|07-并发高频面试题]]