CompletableFuture 是 Java 8 扔给我们的并发“神器”,比 Future 能打多了。链式调用、异步回调,简直是程序员的“懒人福音”。对 JUC 老手来说,它是并发编程的“瑞士军刀”。但它底层咋实现的?别怕,今天我带你扒开它的“衣服”,看看里面藏着啥秘密。
1. 设计理念:从“收据”到“接力赛”
CompletableFuture 不是那种老实巴交的 Future,只给你张“任务收据”然后让你干等着。它更像个跑接力赛的选手:任务跑完一棒,自动把 baton(结果)递给下一棒,还能顺手处理异常。实现上,它靠这几招:
- 任务接龙:支持链式调用(
thenApply、thenCompose等),通过回调机制实现任务依赖。 - 状态驱动:以结果状态为核心,动态触发后续操作。即结果出来就撒腿跑后续操作,不用你喊“开始”。
- 线程池撑腰:默认靠
ForkJoinPool.commonPool(),省得你自己养线程。 - 无锁并发:利用 CAS(Compare-And-Swap)操作,避免传统锁的开销。
比起 FutureTask 的“单打独斗”,CompletableFuture 是个“社交达人”,擅长拉帮结派干大事。它不像 FutureTask那样简单封装一个任务,而是设计了一个灵活的“任务接力系统”。
2. 数据结构:简简单单两件套
与 FutureTask 的单任务封装不同,CompletableFuture 更像一个“任务协调器”,其底层实现围绕状态管理和回调栈展开。CompletableFuture 的核心字段非常精简,家底不多,就两个核心字段:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
volatile Object result; // 任务结果or异常,啥都能装
volatile Completion stack; // 回调栈,干活的“后援团”
// ... 其他辅助字段和方法
}
2.1 result:状态与结果的统一载体:万能“口袋”
- 啥都能装:没结果是
null,正常结果是个对象,异常就塞个AltResult(内部类,专门抱Throwable)。 - 状态暗示:不用显式的
state整数,result自己会“说话”:null:任务未完成。哥还没干完,别催。AltResult:任务异常完成(AltResult是一个内部类,包装Throwable)。我翻车了,抱歉。- 其他非
null值:任务正常完成,结果即该值。搞定,拿走吧!
- 线程安全:靠 CAS(
UNSAFE.compareAndSwapObject)更新,谁抢到谁说了算。
2.2 stack:回调任务的栈结构:即回调的“待办清单”
- 是个栈:用
Completion一个抽象类,表示后续操作的节点。给节点串起来,后进先出,像摞盘子。 - 干啥用:存着依赖当前任务的“后援团”,比如
thenApply的下一步操作。 - 长啥样:单向链表,采用栈的形式(LIFO,后进先出)后面会细说。
- 实现:
Completion的子类(如UniApply、BiConsumer)具体定义回调逻辑。
2.3 Completion:小弟们的“身份证”
abstract static class Completion extends ForkJoinTask<Void> implements Runnable, CompletableFuture.AsynchronousCompletionTask {
Completion next; // 指向下一个回调节点
abstract CompletableFuture<?> tryFire(int mode); // 执行回调逻辑
}
- 抽象基类:每个节点是个
Completion,有next指针指向下家。 - 分工明确:子类各司其职:
UniApply:管thenApply,加工结果。BiConsumer:管thenCombine,合并任务。UniExceptionally:救火队员,处理异常。
- 每个
Completion节点封装了回调函数和目标CompletableFuture。
这帮小弟就像等你下班的同事,任务一完,他们就冲上来喊:“轮到我了吧!”
3. 状态管理与完成机制:没有 state 的“隐形状态机”
不像 FutureTask 老老实实弄个 state 整数,CompletableFuture 玩得花哨,直接用 result 表示状态。咋完成的呢?
3.1 complete(T value):喊“搞定”
- 流程:
- CAS 把
result从null改成value,抢到了就成功。 - 如果成功,调用
postComplete()触发回调。(成功后喊一嗓子postComplete(),唤醒后援团。)
- CAS 把
- 源码片段:
public boolean complete(T value) { boolean triggered = completeValue(value); if (triggered) postComplete(); return triggered; }
就像外卖到了,你签个字,然后通知楼下等饭的同事。
3.2 completeExceptionally(Throwable ex):异常完成
- 流程:将
result设置为new AltResult(ex),然后触发postComplete()。
任务翻车了,它还不忘安慰你:“别慌,我给你留了个错误日志!”
3.3 postComplete():回调触发
- 干啥:在任务完成后,遍历
stack,执行所有依赖回调。 - 源码片段:
final void postComplete() { Completion h; while ((h = stack) != null) { if (UNSAFE.compareAndSwapObject(this, STACK, h, null)) { while (h != null) { h.tryFire(NESTED); // 点火! h = h.next; } } } } - 细节:CAS 清栈,防止其他线程捣乱即并发修改,然后挨个执行
tryFire(),有点像放鞭炮,噼里啪啦。
4. 链式调用的实现:接力赛的“魔法”
CompletableFuture 的链式调用(如 thenApply)是其核心特性,底层依赖回调栈和动态任务创建。
4.1 thenApply 的流程
- 方法签名:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) - 实现逻辑:
- 创建一个新的
CompletableFuture<U>(结果容器)。 - 构造
UniApply节点,封装fn和目标CompletableFuture。 - 检查当前
result:- 已完成:立即执行
fn,设置新结果。(有结果:立刻加工,交给新 Future。) - 未完成:将
UniApply推入stack。(没结果:扔进stack,等着。)
- 已完成:立即执行
- 创建一个新的
- 源码片段:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn) { return uniApplyStage(null, fn); } private <U> CompletableFuture<U> uniApplyStage(Executor e, Function<? super T, ? extends U> f) { CompletableFuture<U> d = new CompletableFuture<U>(); if (result != null) { uniApplyNow(result, d, f); // 立即执行 } else { push(new UniApply<T,U>(e, d, this, f)); // 入栈等待 } return d; }
4.2 异步版本:thenApplyAsync
- 区别:将
UniApply提交到线程池(默认ForkJoinPool.commonPool()),通过e.execute()异步执行。即把UniApply扔给线程池跑,别挡主路。
5. 线程安全与并发控制
CompletableFuture 的线程安全完全依赖无锁设计:
-
CAS 操作:
- 使用
sun.misc.Unsafe的compareAndSwapObject更新result和stack。 - 示例:
static final sun.misc.Unsafe UNSAFE; static final long RESULT = UNSAFE.objectFieldOffset(CompletableFuture.class.getDeclaredField("result")); UNSAFE.compareAndSwapObject(this, RESULT, null, value);
- 使用
-
优点:避免锁竞争,适用于高并发场景。
-
缺点:CAS 失败时可能重试,极端情况下性能下降。
-
阻塞与唤醒:
get()使用LockSupport.park()阻塞调用线程。postComplete()用LockSupport.unpark()唤醒等待线程。
6. 获取结果:get() 的实现
- 逻辑:
- 检查
result,已完成则返回结果或抛异常。 - 未完成则调用
await()阻塞。
- 检查
- 源码片段:
有了就返回,没就睡大觉。这货还挺有耐心,结果没出来就死等,跟排队买奶茶似的。public T get() throws InterruptedException, ExecutionException { Object r; if ((r = result) == null) { await(); // 阻塞等待 r = result; } if (r instanceof AltResult) { throw new ExecutionException(((AltResult) r).ex); } return (T) r; }
7. 线程安全:CAS 大法好
- 无锁狂欢:全靠
sun.misc.Unsafe的 CAS,更新result和stack。- 示例:
UNSAFE.compareAndSwapObject(this, RESULT, null, value)。 - 优点:快如闪电,没锁的拖累。
- 缺点:抢不过就得重试,运气不好可能多跑几圈。
- 示例:
- 阻塞与唤醒:
get()用LockSupport.park()睡一觉。postComplete()用unpark()喊醒,像闹钟一样准时。
7. 性能优化与设计考量
- 栈结构(LIFO):比队列更高效,减少内存分配。LIFO 省内存,操作快。
- 延迟执行:回调尽量在结果可用时立即执行,减少线程切换。即能跑就跑,结果一出来就干活,少切换线程。
- ForkJoinPool 集成:默认线程池支持工作窃取,即“偷活”,效率满分。适合短任务。
- 内存可见性:
volatile修饰result和stack,确保多线程可见。
8. 与 FutureTask 的对比
| 特性 | FutureTask | CompletableFuture |
|---|---|---|
| 任务支持 | 单任务封装 | 支持任务链和组合 |
| 状态管理 | 显式 state 整数 | 通过 result 间接管理 |
| 并发控制 | 锁(同步块) | 无锁(CAS) |
| 回调机制 | 无 | 支持丰富回调(Completion) |
9. 总结
CompletableFuture 底层是个“小怪兽”:
result和stack撑起状态和回调。- CAS 无锁玩得飞起。
Completion链表串起接力赛。ForkJoinPool当苦力。
对 JUC 老手来说,它是并发编程的“瑞士军刀”。理解这些底层细节,下次写异步代码就能更得心应手——毕竟,知道它咋跑的,才能让它跑得更快!