Future 与 CompletableFuture 异步编程

共 26 题
📑 题目列表 26 题
#
★★★

1. CompletableFuture 与自定义 Executor 组合时,链路追踪上下文丢失会导致什么问题,包装 Runnable 的方案如何保证清理

CompletableFuture 与自定义 Executor 组合时,链路追踪上下文丢失会导致什么问题?包装 Runnable 的方案如何保证清理?

  • 上下文丢失
  • 链路追踪
  • 包装 Runnable + 清理

CompletableFuture 用自定义 Executor 执行任务时,任务在 Executor 线程池中运行,ThreadLocal 类上下文(如 TraceId、MDC)不会自动从调用线程传递到执行线程,导致链路追踪断链、日志无法关联。包装 Runnable 方案:用包装器在任务执行前从提交线程捕获上下文、设置到执行线程,执行后清理(remove 防止泄漏)。关键是保证清理:在 finally 中清除 ThreadLocal,否则线程池复用导致上下文泄漏到其他任务。可用 TransmittableThreadLocal 或手写包装器。

上下文丢失是异步/线程池的常见问题。包装 Runnable 需"捕获-设置-清理"三步,清理尤其重要。

Runnable wrap(Runnable task, Map<String, String> ctx) {
    return () -> {
        MDC.setContextMap(ctx);
        try { task.run(); } finally { MDC.clear(); }
    };
}
#
★★★

2. CompletableFuture 的 allOf/anyOf 与 thenApply 组合

CompletableFuture 的 allOf/anyOf 与 thenApply 组合是什么?

  • allOf
  • anyOf
  • thenApply 组合

allOf(f1, f2, ...) 返回一个 CompletableFuture,当所有 future 都完成时完成(不携带结果,需各自 join 取结果);anyOf(...) 返回当任一 future 完成时完成的 future(结果类型 Object)。thenApply 是单 future 的映射(转换结果)。组合:allOf 用于"等待所有并行任务完成",然后 thenApply/thenRun 做后续处理;anyOf 用于"任一完成即继续"(如多个源取最快)。注意 allOf 的结果要通过 join 各 future 获取,且异常会传播。

allOf 聚合"全部完成",anyOf 聚合"任一完成",thenApply 做结果转换。组合实现并行聚合后的处理。

#
★★★

3. CompletableFuture 的 getNow/complete/completeExceptionally

CompletableFuture 的 getNow/complete/completeExceptionally 是什么?

  • getNow
  • complete
  • completeExceptionally

getNow(valueIfAbsent):立即返回结果,若未完成则返回默认值(不阻塞)。complete(value):手动完成 future,返回是否成功(若已完成返回 false);completeExceptionally(ex):手动以异常完成,同样返回是否成功。联合使用:getNow 用于"非阻塞取值",complete 用于"手动完成/覆盖",completeExceptionally 用于"手动失败"。注意 complete 对已完成的 future 无效(返回 false),需用 obtrudeValue 才能强制覆盖(不推荐)。

getNow 非阻塞取默认值,complete/completeExceptionally 手动完成。它们让 future 可被外部驱动完成。

#
★★★

4. CompletableFuture 的 toCompletableFuture 转换

CompletableFuture 的 toCompletableFuture 转换是什么?

  • toCompletableFuture
  • CompletionStage 转换
  • 用途

toCompletableFuture() 是 CompletionStage 接口的方法,把 CompletionStage 转换为 CompletableFuture。由于 CompletableFuture 实现 CompletionStage,通常直接返回自身(若已是 CompletableFuture)。用途:当代码只暴露 CompletionStage 类型时,需要调制(调 complete、join、get 等 CompletableFuture 特有方法)时转为 CompletableFuture。注意:转换后得到的 CompletableFuture 与原始 stage 的完成状态关联(若原 stage 是自定义实现,转换可能产生新的 CF)。

toCompletableFuture 把 CompletionStage 转成 CompletableFuture,以便使用其独占方法。多数情况返回自身。

#
★★★

5. CompletableFuture 的异常处理(exceptionally/handle/whenComplete)

CompletableFuture 的异常处理(exceptionally/handle/whenComplete)是什么?

  • exceptionally
  • handle
  • whenComplete

exceptionally(fn):仅在异常时执行,返回替代值(类似 catch,fn 返回值类型与原结果一致)。handle(fn):无论成败都执行,fn 接收 (result, ex),返回新值(可转换类型,类似 finally+转换)。whenComplete(fn):无论成败都执行,fn 消费 (result, ex),不改变结果(原 future 的结果/异常继续传播,类似 finally)。差异:exceptionally 只处理异常、handle 可转换结果、whenComplete 只观察不改变。按"是否需要转换结果"选择。

三者是异步异常处理的不同粒度:exceptionally 兜底、handle 转换、whenComplete 观察。handle 最灵活。

#
★★★

6. CompletableFuture 的循环链(loop chain)内存风险

CompletableFuture 的循环链(loop chain)内存风险是什么?

  • 循环链
  • 内存风险
  • 治理

若用 CompletableFuture 构造无限循环(如 thenCompose 递归调用自身),每次迭代创建新的 CF 节点并链接,形成不断增长的链,可能造成内存泄漏或栈溢出(递归深度)。风险:链式依赖的节点被持续引用,无法被 GC 回收;深层递归导致栈溢出。治理:避免递归构造 CF 链,改用显式循环(迭代提交)、设置终止条件、限制深度、用完成回调而非链式递归。CF 适合有限任务编排,不适合无限递归。

循环链是"无限递归创建 CF"的内存/栈风险。用迭代或终止条件替代递归链。

#
★★★

7. CompletableFuture 的链式调用(thenApply/thenCompose/thenCombine)

CompletableFuture 的链式调用(thenApply/thenCompose/thenCombine)是什么?

  • thenApply
  • thenCompose
  • thenCombine

thenApply(fn):上一步结果映射为新值(同步转换,返回 CF)。thenCompose(fn):上一步结果映射为新的 CF(异步扁平化,返回 CF,避免嵌套 CF)。thenCombine(other, fn):两个 CF 都完成后,组合各自结果(并行聚合)。差异:thenApply 转换值、thenCompose 扁平化嵌套、thenCombine 组合两个独立结果。链式编排:thenApply 串行映射、thenCompose 串行依赖异步、thenCombine 并行组合。

三者是 CF 链式编排的核心:thenApply 映射、thenCompose 扁平化、thenCombine 组合。理解返回值差异是关键。

#
★★★

8. CompletableFuture 链中阻塞操作的影响

CompletableFuture 链中阻塞操作的影响是什么?

  • 阻塞操作
  • 链式影响
  • 线程池占用

在 CF 链的某个阶段(如 thenApply 内)执行阻塞操作(IO、锁、长计算),会阻塞执行该阶段的线程(CommonPool 或自定义 Executor 的线程)。若链上多个阶段阻塞,或在固定线程池中大量阻塞,会耗尽线程池线程,导致后续任务排队、整体延迟上升。影响:1) 阻塞的线程被占用,其他任务无法执行;2) 阻塞与异步模型冲突,违背非阻塞原则。缓解:避免在链中做阻塞操作,用异步阶段(thenApplyAsync)或在阻塞的 Executor 中执行,虚拟线程下用虚拟线程池。

CF 链中阻塞会占用线程池线程,可能耗尽。应避免阻塞,或把阻塞隔离到专用线程池/虚拟线程。

#
★★

9. CompletableFuture 默认 ForkJoinPool.commonPool 与自定义 Executor 组合时的上下文切换成本

CompletableFuture 默认 ForkJoinPool.commonPool 与自定义 Executor 组合时的上下文切换成本是什么?

  • commonPool
  • 自定义 Executor
  • 上下文切换

CF 默认用 commonPool(并行度=核数-1)执行异步阶段。若与自定义 Executor 组合(thenApplyAsync(executor)),任务在多个线程池间切换执行,带来上下文切换成本:线程在线程池间迁移、缓存失效、调度开销。若 commonPool 与业务线程池混用,或是 commonPool 被阻塞任务占满,会造成饥饿与竞争。成本考量:1) 每阶段切换线程有切换成本;2) commonPool 被占用影响其他使用它的任务;3) 建议显式指定 Executor 以隔离资源、避免 commonPool 竞争。

commonPool 与自定义 Executor 混用有切换成本与资源竞争。显式指定 Executor 隔离资源更可控。

#
★★

10. CompletionStage(JDK 8+)与 CompletableFuture 在组合异步任务时的链式 API 设计

CompletionStage(JDK 8+)与 CompletableFuture 在组合异步任务时的链式 API 设计是什么?

  • CompletionStage 接口
  • 链式 API
  • 设计

CompletionStage 是异步计算阶段的接口,CompletableFuture 是其实现。CompletionStage 提供丰富的链式 API:转换(thenApply)、消费(thenAccept)、运行(thenRun)、组合(thenCompose/thenCombine/thenAcceptBoth)、异常(exceptionally/handle/whenComplete)、任意/全部(anyOf/allOf)。设计目标:以声明式方式编排异步任务,表达"当 A 完成则做 B、A 与 B 都完成则组合 C",避免回调地狱。API 以"完成时触发后续"为模型,支持回调、异步、依赖编排。

CompletionStage 的链式 API 用"完成驱动"表达异步依赖,是函数式异步编排的基础。

#
★★

11. ExecutorService.submit 返回 Future 与 Executor.execute 的异常处理差异

ExecutorService.submit 返回 Future 与 Executor.execute 的异常处理差异是什么?

  • submit 异常
  • execute 异常
  • 差异

submit(Runnable) 返回 Future,任务抛出的异常被封装在 Future 中,调用 future.get() 时抛 ExecutionException 才能捕获;execute(Runnable) 不返回 Future,任务异常直接抛给线程,由线程的 UncaughtExceptionHandler 处理(或打印到 stderr)。差异:submit 异常延迟到 get() 暴露,execute 异常立即暴露(经 Handler)。submit 适合需要获取结果/异常或管理任务;execute 适合 fire-and-forget(异常由 Handler 兜底)。若用 submit 不调 get(),异常会被静默吞掉。

submit 把异常装进 Future(get 才暴露),execute 立即抛给 Handler。submit 不 get 会吞异常。

#
★★

12. Future 接口的限制(阻塞、取消、组合)

Future 接口的限制(阻塞、取消、组合)是什么?

  • 阻塞 get
  • 取消
  • 无法组合

Future 接口的限制:1) 阻塞:get() 阻塞直到结果或异常,无法回调或非阻塞;2) 取消:cancel(mayInterruptIfRunning) 只能协作式中断,无法强制停止,且无法区分"取消"与"异常";3) 组合:无法把多个 Future 组合(如聚合、依赖),只能逐个 get;4) 无回调:结果就绪不会通知。这些限制使 Future 难以编排复杂异步任务,催生了 CompletableFuture(组合、回调、非阻塞)。Future 适合单个任务的结果获取。

Future 的限制是"阻塞、难取消、不能组合",CompletableFuture 弥补这些不足。

#
★★

13. Future 链式调用缺失与 CompletableFuture 的对比

Future 链式调用缺失与 CompletableFuture 的对比是什么?

  • Future 无链式
  • CompletableFuture 链式
  • 对比

Future 缺乏链式调用:无法表达"A 完成后做 B、B 完成后做 C"的依赖,只能手动 get 后串行,或层层嵌套回调,代码复杂。CompletableFuture 提供链式 API(thenApply/thenCompose 等),以声明式表达异步依赖,结果自动传递,支持组合、异常处理、超时。对比:Future 是"拉取"(阻塞 get),CompletableFuture 是"推送"(完成驱动回调);Future 单任务,CompletableFuture 可编排。复杂异步编排用 CompletableFuture。

Future 无链式编排,CompletableFuture 以链式 API 表达依赖,是异步编程的演进。

#
★★

14. Future.cancel(mayInterruptIfRunning) 的真实行为

Future.cancel(mayInterruptIfRunning) 的真实行为是什么?

  • cancel 语义
  • mayInterruptIfRunning
  • 边界

cancel(mayInterruptIfRunning):1) 若任务尚未开始(排队中),取消并移除,任务不执行;2) 若任务已开始且 mayInterruptIfRunning=true,调用执行线程的 interrupt()(协作式中断,不强制停止);3) 若任务已开始且 mayInterruptIfRunning=false,不中断,任务继续执行到完成,但 Future 状态标记为取消(get 抛 CancellationException);4) 若任务已完成/已取消,cancel 返回 false。真实行为:cancel 是"协作式",无法强制终止已在运行的任务,只能中断提示或标记取消。

cancel 对排队任务有效,对运行中任务只能中断提示或标记取消,无法强制停止。是否真正停止取决于任务响应中断。

#
★★

15. Future.get() 在 JDK 25 中对中断(Interrupt)与虚拟线程的取消传播机制

Future.get() 在 JDK 25 中对中断(Interrupt)与虚拟线程的取消传播机制是什么?

  • get 中断
  • 虚拟线程
  • 取消传播

Future.get() 会响应中断:调用线程被 interrupt 时抛 InterruptedException(调用方需处理)。JDK 25 中,虚拟线程调用 get() 阻塞时,会卸载载体线程(不占用平台线程),等待期间可被中断。取消传播:调用方 cancel() 时,若任务在虚拟线程执行,interrupt 会传播到虚拟线程,虚拟线程响应中断后停止。JDK 25 的机制是:get 阻塞对虚拟线程友好(可卸载),中断与取消通过 interrupt 协作传播,虚拟线程需正确响应中断。

get() 可中断、虚拟线程阻塞可卸载,取消靠 interrupt 协作传播。虚拟线程需响应中断才能停止。

#
★★

16. Future.get() 的可中断与超时

Future.get() 的可中断与超时是什么?

  • get 中断响应
  • 超时 get
  • 处理

Future.get() 无参:阻塞直到结果或异常,可被中断(抛 InterruptedException)。get(timeout, unit):带超时,超时抛 TimeoutException,需处理超时后取消任务或继续等待。可中断与超时配合:get(timeout) 时若超时,可调用 cancel(true) 尝试取消任务。调用方应处理 InterruptedException(恢复中断标志)与 TimeoutException(超时策略)。超时 get 是防止无限等待的关键。

get() 可中断,get(timeout) 可超时。超时后需决定取消或降级,避免无限等待。

#
★★

17. FutureTask 的状态机与实现

FutureTask 的状态机与实现是什么?

  • FutureTask 状态
  • 状态机
  • 实现

FutureTask 内部用状态(volatile int state)管理:NEW(初始)、COMPLETING(结果写入中)、NORMAL(正常完成)、EXCEPTIONAL(异常完成)、CANCELLED(取消)、INTERRUPTING(中断中)、INTERRUPTED(已中断)。状态转换:NEW→COMPLETING→NORMAL/EXCEPTIONAL(完成);NEW→CANCELLED(取消);NEW→INTERRUPTING→INTERRUPTED(中断)。通过 CAS 状态转换 + WaitNode 等待队列(等待者 park)实现。get() 等待直至状态非 NEW,结果/异常封装返回。它是 Future 与 Runnable 的桥接(RunnableFuture)。

FutureTask 状态机用 CAS 原子转换,保证"完成/取消/中断"的确定性与等待者唤醒。是 Future 的底层实现。

#
★★

18. JDK 21 虚拟线程与 CompletableFuture 的搭配

JDK 21 虚拟线程与 CompletableFuture 的搭配是什么?

  • 虚拟线程
  • CompletableFuture
  • 搭配

虚拟线程与 CompletableFuture 搭配:1) 用虚拟线程执行 CF 的阻塞阶段(如网络 IO),虚拟线程阻塞时卸载,不占载体线程,提高并发;2) 可给 CF 指定虚拟线程 Executor(Executors.newVirtualThreadPerTaskExecutor),让异步阶段在虚拟线程上运行;3) 虚拟线程简化了"IO 阻塞"的异步处理,但 CF 的链式编排仍有用。搭配意义:虚拟线程解决阻塞利用率,CF 解决编排,两者互补。对大量 IO 阻塞的异步任务,虚拟线程可替代部分回调式异步。

虚拟线程(阻塞卸载)与 CF(编排)互补。虚拟线程执行阻塞阶段,CF 负责组合。

#
★★

19. JDK 25 中 CompletableFuture 对虚拟线程的执行策略(virtualThreadExecutor)支持

JDK 25 中如何让 CompletableFuture 的异步阶段在虚拟线程上执行?其虚拟线程执行策略是什么?

  • 显式指定虚拟线程 Executor(Executors.newVirtualThreadPerTaskExecutor)
  • 异步阶段的执行策略
  • 与默认 commonPool 的对比

CompletableFuture 的异步阶段(thenApplyAsync 等)默认在 commonPool(平台线程池)上执行;要让异步阶段跑在虚拟线程上,应显式传入虚拟线程 Executor,例如 CompletableFuture.supplyAsync(supplier, Executors.newVirtualThreadPerTaskExecutor()) 或 thenApplyAsync(fn, executor)。虚拟线程执行异步阶段时,阻塞操作可卸载载体线程、不占平台线程,适合 IO 密集的异步链,可缓解异步阶段在 commonPool 上阻塞导致线程耗尽的问题。注意:JDK 中并不存在名为 virtualThreadExecutor() 的 CompletableFuture 静态方法(这是网上流传的说法,未进入 JDK),官方支持方式是显式传入 Executors.newVirtualThreadPerTaskExecutor() 作为 Executor。

让 CF 异步阶段在虚拟线程上执行需通过"显式传入虚拟线程 Executor"实现:阻塞可卸载,改善 IO 密集异步链的并发,同时避免 commonPool 被阻塞任务占满。

#
★★

20. JDK 25 中虚拟线程在 I/O 阻塞场景下对 Future 异步模型的简化价值

JDK 25 中虚拟线程在 I/O 阻塞场景下对 Future 异步模型的简化价值是什么?

  • 虚拟线程
  • I/O 阻塞
  • 简化异步

传统 Future 异步模型用回调/链式处理 I/O 阻塞,因为平台线程昂贵、阻塞会浪费线程。虚拟线程让"阻塞"变廉价:线程可用 blocking 代码(如直接调用阻塞 API),虚拟线程阻塞时卸载载体线程,无需回调。因此虚拟线程在 I/O 阻塞场景下简化了异步模型:用同步代码写 I/O,无需 Future/CompletableFuture 的复杂编排,获得高并发。价值:代码更简单、易读、少回调地狱。

虚拟线程让"阻塞即卸载",I/O 场景可用同步代码,简化异步模型。Future 异步降为辅助。

#
★★

21. CompletionStage 接口的设计目标

CompletionStage 接口的设计目标是什么?

  • 设计目标
  • 异步阶段
  • 契约

CompletionStage 的设计目标:表达"异步计算的阶段",提供依赖编排与结果传播的契约。核心:1) 一个阶段完成时触发后续阶段(完成驱动);2) 结果/异常沿链传播;3) 支持转换、消费、组合、超时、异常处理;4) 与执行环境解耦(异步方法可指定 Executor)。设计上讲,CompletionStage 是异步流水线的"节点",CompletableFuture 是其实现。目标是用声明式 API 替代回调和阻塞,实现可组合、可扩展的异步编程。

CompletionStage 是"异步阶段"的抽象,目标是声明式编排、结果传播、可组合。CompletableFuture 是主力实现。

#
★★

22. JDK 25 的 StructuredTaskScope 与 ExecutorService 在异常传播与取消语义上的根本差异是什么,为何被认为是 Future 的现代替代

JDK 25 的 StructuredTaskScope 与 ExecutorService 在异常传播与取消语义上的根本差异是什么?为何被认为是 Future 的现代替代?

  • StructuredTaskScope
  • 异常传播
  • 取消语义

StructuredTaskScope(结构化并发)与 ExecutorService 的根本差异:1) 生命周期:SScope 定义任务作用域,所有子任务在作用域内完成,作用域结束自动等待/join 所有子任务;2) 异常传播:子任务异常聚合到父作用域,可统一处理(ShutdownOnFailure 等策略),异常在结构内传播;3) 取消语义:子任务失败或取消时,结构化地级联取消其他子任务(短路),避免孤立任务;4) 结构:调用栈与任务结构对应,便于管理与诊断。为何替代 Future:Future 是"无主"的、任务生命周期与调用者解耦、异常需手动 get、取消不联动;SScope 提供结构化、可取消、异常聚合的模型,更符合现代并发最佳实践。

SScope 用"结构化生命周期 + 异常聚合 + 级联取消"解决 Future 的"无主、难取消、异常分散"问题。

#
★★

23. copy()(JDK 9+)与 minimalCompletionStage

copy()(JDK 9+)与 minimalCompletionStage 是什么?

  • copy()
  • minimalCompletionStage
  • 用途

copy() 返回当前 CompletableFuture 的副本(新 CF,完成状态与当前一致),用于隔离:副本完成不影响原 future(除非用 obtrude),适合把 future 传递给不可信代码。minimalCompletionStage() 返回一个"最小"的 CompletionStage:只暴露 CompletionStage 接口(不能 complete/join/get 等 CF 专属方法),防止调用方修改或阻塞,用于"只读"地暴露异步结果。两者都是"保护性"转换:copy 隔离、minimal 限制能力。

copy 复制隔离,minimalCompletionStage 降级为只读 CompletionStage。两者用于控制对外暴露的能力。

#

24. JDK 25 虚拟线程下 CompletableFuture 的演进

JDK 25 虚拟线程下 CompletableFuture 的演进是什么?

  • 虚拟线程
  • CF 演进
  • 执行策略

JDK 25 虚拟线程下 CompletableFuture 的实践演进:1) CF 异步阶段可在虚拟线程上执行(阻塞可卸载),方式是显式传入 Executors.newVirtualThreadPerTaskExecutor() 作为 Executor(JDK 并未提供 virtualThreadExecutor() 静态方法);2) CF 与虚拟线程搭配,IO 密集异步链更高效;3) 语义保持稳定,无破坏性变更。实践方向:让 CF 的异步执行适配虚拟线程调度,解决 commonPool 阻塞耗尽问题。使用场景:大量 IO 阻塞的异步任务,用虚拟线程执行 CF 阶段,提升并发与简单性。

JDK 25 CF 与虚拟线程的搭配是"显式指定虚拟线程 Executor",提升 IO 密集异步并发。

#

25. JDK 25 中 StructuredTaskScope(JEP 505,5th Preview)作为 Future 替代的 API 设计要点

JDK 25 中 StructuredTaskScope(JEP 505,5th Preview)作为 Future 替代的 API 设计要点是什么?

  • StructuredTaskScope API
  • fork/join
  • 策略

StructuredTaskScope(JEP 505,JDK 25 第 5 次预览)API 设计要点:1) 用 try-with-resources 定义作用域;2) fork(subtask) 创建子任务,返回 Subtask;3) join() 等待所有子任务完成;4) 策略类(ShutdownOnFailure、ShutdownOnSuccess)控制"任一失败即停止/任一成功即停止"的短路行为;5) 子任务异常聚合,通过 scope 处理;6) 子任务在作用域内运行,作用域结束自动管理其生命周期;7) 支持 Scoped Values 传递上下文。设计目标是"结构化、可取消、异常聚合"的并发,替代 Future 的无主异步。

SScope API 用作用域+策略+Subtask 表达结构化并发,支持短路取消与异常聚合,是 Future 现代替代。

#

26. 异步任务编排的取消与超时,orTimeout/completeOnTimeout 与调用方取消的传播边界如何?

异步任务编排的取消与超时:orTimeout/completeOnTimeout 与调用方取消的传播边界是什么?

  • orTimeout
  • completeOnTimeout
  • 取消传播边界

orTimeout(duration):超时后以 TimeoutException 完成该 CF(不取消底层任务,只标记超时)。completeOnTimeout(value, duration):超时后以默认值完成。两者都是"为 CF 设置超时":超时仅改变 CF 的完成结果,不取消正在执行的任务——底层任务会继续执行,只是结果被忽略。调用方取消(cancel)的传播边界:cancel 会影响 CF 链,但不会中断底层已开始的任务(除非任务响应中断)。传播边界:超时/取消只作用于 CF 状态,不强制停止底层任务,需任务自身响应中断或检查取消状态。

orTimeout/completeOnTimeout 只改 CF 完成,不取消底层任务;取消传播需任务协作响应中断。超时后任务仍在跑。