题目:CompletableFuture的底层是如何实现的?
难度:medium
标签:java/juc, java/concurrency
评分:⭐☆☆☆☆(1/5)
CompletableFuture是用于实现异步编程的工具类,核心是异步执行任务,非阻塞的回调,执行任务编排;
优点:定位说对了——异步 + 回调 + 编排
不足:几乎没触及底层:① 未提 result 字段 + volatile 可见性;② 未提依赖栈 stack(Treiber 栈) 存储回调,完成时 postComplete 弹栈触发;③ 未提 Completion 链表与 UniApply/UniAccept/UniCompose 等节点类型;④ 未提 ASYNC 后缀与 ForkJoinPool.commonPool() / 自定义 Executor;⑤ 未提 thenApply vs thenApplyAsync 的线程语义差异;⑥ 未提异常传播(whenComplete/handle/exceptionally)
补充:一句话——CompletableFuture = volatile Object result + Treiber 回调栈 + CAS 状态机;同步回调在完成线程执行,Async 变体提交到线程池
核心考点定位:能否说清 result 字段、回调栈、完成触发链路,以及同步/异步回调的线程语义。
知识点拆解:
volatile Object result(null=未完成,NIL=无结果完成,AltResult=异常完成);volatile Completion stack(Treiber 栈,LIFO)thenApply 等把动作封装成 UniApply/UniAccept/UniCompose/UniRelay 节点,push 到 stack;若已存在结果则直接 postCompletecomplete() CAS 设置 result,然后 postComplete() 依次 pop 节点并执行,执行时还会处理"依赖的依赖"(嵌套触发)thenApply 在完成它的那个线程上执行(可能是主线程,也可能是 IO 线程);thenApplyAsync 提交到 ForkJoinPool.commonPool() 或指定 ExecutorAltResult,沿链路传播;exceptionally 兜底,handle 同时处理正常与异常,whenComplete 不吞异常按「底层原理 → 设计权衡 → 工程实践」递进排列。
考察方向:
Future、CompletableFuture、虚拟线程 + StructuredTaskScope 的对比延伸递进面试题:
thenApply 和 thenApplyAsync 的区别?allOf 怎么实现等待全部完成?BiRelay 节点递减计数,计数归零时完成聚合 futureget() 阻塞失去异步意义【一句话结论】
面试官您好,CompletableFuture 本质是一个 volatile 结果字段加一个无锁回调栈,完成时把栈里的回调依次弹出来执行。
【三点展开】
第一,它只有两个核心字段:volatile Object result 记录结果,未完成是 null,异常完成是 AltResult;volatile Completion stack 是个 Treiber 栈,用 CAS 做无锁 push。
第二,thenApply 这类方法会把动作包成 UniApply 节点压栈;如果此时已经完成,就直接进 postComplete 弹栈执行,所以注册和执行没有固定顺序,靠 result 的 CAS 状态机来同步。
第三,线程语义要分清:thenApply 跑在"完成它的那个线程"上,可能是业务线程也可能是 IO 线程;thenApplyAsync 才提交到 commonPool 或你指定的池。线上 IO 密集场景一定要自定义池,否则 commonPool 只有 CPU 核数减一个线程,很容易被打满。
【一句边界】
当然,它不适合做长链路阻塞编排——默认池不可控、异常容易静默丢失;JDK 21 之后更推荐虚拟线程 + StructuredTaskScope 来做结构化并发。