从 Future 到 CompletableFuture

Java 5 引入的 Future 只能阻塞式 get 结果,要么一直等,要么轮询 isDone,想编排多个异步任务非常别扭。Java 8 的 CompletableFuture 补齐了短板:既能异步执行,又能链式回调、组合多个任务、统一处理异常,把异步编程从「回调地狱」变成「流水线」。

CompletableFuture 实现了 CompletionStage 接口,核心思想是把任务看成阶段:一个阶段的输出自动变成下一阶段的输入。每个方法都返回新的 CompletableFuture,所以可以无限链下去,写起来像声明式的流程描述,而不是层层嵌套的回调。

它和 Future 的最大区别在于「完成时」的处理。Future 要你主动去要结果,CompletableFuture 可以注册回调,任务一完成回调自动执行。这种「推」而不是「拉」的模型,让响应式编程成为可能。

使用场景非常明确:一个请求要同时调用户服务、订单服务、商品服务,三个结果拼起来返回。串行要几百毫秒,并行只要最慢的那个时间。这种「扇出-聚合」模式是 CompletableFuture 的主场。

Future 让你等结果,CompletableFuture 让结果来找你。
本文核心知识点
  • supplyAsync异步执行并返回结果。
  • thenApply串行转换结果。
  • allOf等所有任务完成。
  • exceptionally异常兜底处理。
CompletableFuture 异步并发配图:蓝调渐变背景上多条流线汇聚向中心发光点的抽象科技插画
多个异步任务并行汇聚,结果一起返回。

异步任务的创建

创建异步任务主要用 supplyAsync(有返回值)和 runAsync(无返回值)。不传线程池时用公共 ForkJoinPool.commonPool,业务系统建议显式传自己的线程池。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService pool = Executors.newFixedThreadPool(8);

CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
    () -> userService.getById(1001L), pool);

CompletableFuture<Void> logFuture = CompletableFuture.runAsync(
    () -> logger.info("记录访问日志"), pool);

也可以手工创建一个未完成的 CompletableFuture,在别处调用 complete 让它完成,常用于把回调 API 包装成异步结果。

链式回调编排

任务之间的依赖关系用 thenApply、thenAccept、thenCompose 串联。thenApply 转换结果,thenAccept 消费结果不返回,thenCompose 用于前一步返回的还是 CompletableFuture 的情况(避免嵌套)。

CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> fetchOrder(1001L), pool)
    .thenApply(order -> order.getAmount())
    .thenApply(amount -> amount * 0.9)      // 打九折
    .thenApply(discount -> "应付金额:" + discount)
    .thenAccept(System.out::println);

thenCompose 解决「回调里又开异步」的嵌套问题:flatMap 的语义,把两步异步拍平成一串。

CompletableFuture<List<Item>> itemsFuture = CompletableFuture
    .supplyAsync(() -> fetchOrder(1001L), pool)
    .thenCompose(order -> CompletableFuture.supplyAsync(
        () -> itemService.listByOrder(order.getId()), pool));

多任务组合

并行聚合用 allOf(等全部完成)或 anyOf(任意一个完成)。allOf 返回 CompletableFuture,要拿各自结果再 join。

CompletableFuture<User> u = CompletableFuture.supplyAsync(() -> userService.getById(1L), pool);
CompletableFuture<Order> o = CompletableFuture.supplyAsync(() -> orderService.getByUser(1L), pool);
CompletableFuture<Coupon> c = CompletableFuture.supplyAsync(() -> couponService.getByUser(1L), pool);

CompletableFuture.allOf(u, o, c).join();   // 三个都完成才继续

User user = u.join();
Order order = o.join();
Coupon coupon = c.join();

如果两个任务结果要合并成一个,用 thenCombine;两个任务要竞争取先完成者,用 applyToEither。这些组合方法让复杂的并发流程写起来就像搭积木。