FutureTask 不依赖 volatile 修饰 outcome 即可保证可见性,核心是通过 volatile state 状态机与 happens-before 规则构建“守门式”同步:写 outcome 前先 CAS 修改 state 为 COMPLETING,再赋值,最后设终态;读 outcome 仅在 state > COMPLETING 时发生,借助 volatile state 的 happens-before 链确保 outcome 写对后续读可见,兼顾性能与正确性。

FutureTask 不靠 volatile 修饰 outcome 就能保证结果对调用 get() 的线程可见,核心在于它用状态机 + happens-before 规则构建了一套“守门式”同步机制。
state 变量是关键守门员
state 是 volatile 的整型变量,定义了任务从 NEW 到 NORMAL/EXCEPTIONAL 等明确的状态变迁路径。所有对 outcome 的写入和读取,都严格绑定在 state 的特定值上:
- 写
outcome只发生在set()或setException()中,且必须先将state通过 CAS 改为COMPLETING,再赋值outcome,最后用putOrderedInt把state设为NORMAL或EXCEPTIONAL - 读
outcome只发生在report()中,而该方法仅在确认state > COMPLETING(即已进入终态)后才执行 - 中间没有代码能绕过
state判断直接访问outcome,相当于用状态做了一道强制屏障
利用 volatile state 建立 happens-before 链
根据 Java 内存模型的 volatile 变量规则,对 state 的写操作(如设为 NORMAL)happens-before 后续对同一 state 的读操作(如 get() 中检测到 state > COMPLETING)。而 outcome = v 在 state 写之前完成,因此也纳入该 happens-before 关系链中——outcome 的写自然对后续读可见。
这种设计避免了给 outcome 加 volatile 带来的每次读写都触发内存屏障的开销,只在结果发布的那一刻完成一次强同步,性价比更高。
立即学习“Java免费学习笔记(深入)”;
多个线程 get 同一结果不会重复计算
FutureTask 允许多个线程同时调用 get(),它们共享同一个 outcome。只要第一个线程完成设置,后续线程在 awaitDone 返回后进入 report(),都能看到已写入的 outcome 值。这是因为:
-
state进入终态后不可逆,所有等待线程被唤醒时看到的是同一份已确定的结果 - 整个流程不依赖锁,但依靠状态跃迁顺序和 volatile 的语义保障了跨线程的数据传递
cancel 和异常路径同样受控
取消任务(CANCELLED)、中断执行(INTERRUPTED)或抛出异常(EXCEPTIONAL)时,outcome 也会被相应设置(如异常对象),且同样遵循“先改 state、再写 outcome、最后定 state 终态”的流程。因此无论任务以何种方式结束,get() 总能安全读到对应结果或异常,不存在读到未初始化或中间状态的 outcome。


















