面试知识库
进阶

CompletableFuture用法#

一句话答案#

CompletableFuture 支持异步编排和组合:supplyAsync→thenApply→exceptionally,比 Future 强大且不阻塞。

核心要点

CompletableFuture vs Future:

Future 的局限:
  ① get() 阻塞 → 失去异步意义
  ② 无法组合多个异步任务
  ③ 无法注册回调(任务完成后自动执行下一步)

CompletableFuture 解决了所有问题 → 非阻塞链式异步编排
plaintext

核心用法:

多任务组合:

生产实践 — 并行查询多个服务:

public UserDetailVO getUserDetail(Long userId) {
    CompletableFuture<User> userFuture = supplyAsync(() -> userService.getById(userId), executor);
    CompletableFuture<List<Order>> orderFuture = supplyAsync(() -> orderService.listByUserId(userId), executor);
    CompletableFuture<Address> addrFuture = supplyAsync(() -> addressService.getByUserId(userId), executor);

    CompletableFuture.allOf(userFuture, orderFuture, addrFuture).join();

    return new UserDetailVO(userFuture.join(), orderFuture.join(), addrFuture.join());
}
// 三个查询并行执行,总耗时 = max(三个查询) 而不是三个之和
java

注意事项:

① 不要用默认的 ForkJoinPool.commonPool(),应指定业务线程池
② supplyAsync 中的异常不会自动抛出,必须用 exceptionally/handle 处理
③ join() 和 get() 的区别:join() 抛 CompletionException(unchecked),get() 抛 checked exception
④ thenApply vs thenApplyAsync:不带 Async 在上一步的线程中执行,带 Async 提交到线程池
plaintext
面试回答(2分钟版)

CompletableFuture 是 Java 8 引入的异步编排工具,解决了 Future.get() 阻塞等待的问题。它的核心能力有三个:第一是链式异步处理,supplyAsync 启动异步任务,thenApply 对结果做转换,thenAccept 消费结果,exceptionally 做异常兜底,整条链非阻塞无需手动 get;第二是多任务组合,allOf 等待所有任务完成、anyOf 取最先完成的结果、thenCombine 合并两个任务的结果;第三是灵活的异常处理,handle 方法可以同时处理正常结果和异常。生产中最典型的用法是并行查询多个服务:用户详情页需要同时查用户信息、订单列表、收货地址,三个 supplyAsync 并行执行后用 allOf 等待全部完成,总耗时等于最慢的那个查询而不是三者之和。关键注意点:一定要指定自定义线程池而不是默认的 ForkJoinPool.commonPool(),因为公共池被一个慢任务阻塞会影响全局;异常不会自动抛出,必须用 exceptionally 或 handle 显式处理。

追问与易错

追问方向:

  • “thenApply 和 thenCompose 的区别?”→ thenApply 是 map(同步转换值),thenCompose 是 flatMap(返回新的 CompletableFuture 避免嵌套),类似 Stream 的 map vs flatMap
  • “异常处理用 exceptionally 还是 handle?”→ exceptionally 只处理异常返回兜底值,handle 同时处理正常结果和异常,需要根据是否要区分正常/异常逻辑来选择
  • “不指定线程池有什么问题?”→ 默认使用 ForkJoinPool.commonPool(),该池全 JVM 共享,一个慢任务阻塞会影响所有使用公共池的操作,生产中必须指定独立业务线程池

易错点:

  • ❌ CompletableFuture 自动处理异常——未捕获的会被静默吞掉
  • ❌ 链太长不拆分——影响可读性