volatile 在 FutureTask 中用于保证 state 字段的可见性与有序性,确保多线程间状态变更及时感知;其核心作用是遵循 JMM 规范建立 happens-before 关系,而非阻止 JIT 错误优化。

volatile 关键字在 FutureTask 中的作用,不是为了“阻断 JIT 编译器的错误优化”,而是确保多线程间对共享状态变量的可见性与有序性——这是 Java 内存模型(JMM)的语义保证,不是 JIT 的“bug”或“错误”,更不是需要去“阻断”的对象。
简单说:JIT 没有做错;volatile 是你主动告诉 JVM:“这个变量的读写必须按 JMM 规则严格同步”。
FutureTask 中 volatile 用在哪儿?
FutureTask 的核心状态字段:
private volatile int state;
它被用于标记任务生命周期的六个关键阶段(如 NEW, COMPLETING, NORMAL, EXCEPTIONAL, CANCELLED, INTERRUPTING)。这个 state 变量是所有线程协作判断任务是否完成、是否可取结果、是否已被取消的唯一依据。
为什么必须用 volatile?不加会怎样?
不加 volatile,可能出现以下真实问题:
- 主线程调用
futureTask.get(),发现state == NEW,于是挂起等待; - 同时工作线程已执行完
callable.call(),正准备setOutcome(result)并更新state = NORMAL; - 但因缺少
volatile,该写操作可能:- 被 CPU 缓存滞留在本地核中,未及时刷回主内存;
- 或被 JIT 重排序(如把
state = NORMAL提前到result赋值前);
- 导致主线程永远看不到
state变更,无限阻塞在get()—— 不是死锁,是可见性丢失。
✅
volatile禁止指令重排序(写之前的所有操作不能重排到写之后),并强制写操作立即刷新到主内存、读操作强制从主内存加载最新值。
volatile 和 JIT 的关系:不是对抗,而是协同
JIT 编译器(如 HotSpot C2)会深度优化代码,例如:
- 将循环中反复读取的非 volatile 字段提升为寄存器缓存(即“读提升”);
- 对无数据依赖的写操作重排序以提升流水线效率。
但 JIT 严格遵守 JMM 规范:一旦你声明 volatile,它就会:
- 禁用对该字段的寄存器缓存优化;
- 插入必要的内存屏障(
membar)指令(如 x86 上的lock addl $0,0(%%rsp)); - 保证
volatile write之前的任意内存操作,对后续volatile read可见(happens-before)。
所以这不是“阻止错误优化”,而是通过标准语义引导 JIT 做正确优化。
实际编码中要注意什么?
- ✅
state字段用volatile是必须的,源码早已如此(JDK 自带实现); - ❌ 不要试图自己用
volatile去“修复”outcome(存储结果的字段)——它本身是非 volatile 的,但其读写被state的 volatile 读写所保护(通过 happens-before 链); - ✅ 若你自定义类似
FutureTask的结构,所有跨线程感知的状态标志(如isRunning,isCancelled)都应声明为volatile; - ⚠️
volatile不能替代锁:它不保证复合操作原子性(如counter++),仅适用于“纯读/纯写+状态跃迁”场景。
补充:FutureTask 的状态流转如何依赖 volatile?
典型流程如下(简化):
- 构造时:
state = NEW(volatile 写 → 对所有线程可见) - 执行线程调用
run():if (state == NEW && UNSAFE.compareAndSet(this, STATE, NEW, COMPLETING))-
outcome = result(普通写) -
state = NORMAL(volatile 写 → 刷新 outcome + 唤醒等待线程)
- 主线程调用
get():- 循环检查
while (state == NEW || state == COMPLETING)(每次都是 volatile 读) - 一旦看到
state >= NORMAL,就安全读取outcome
- 循环检查
整个过程靠 volatile state 建立 happens-before 关系,让 outcome 的写对读可见 —— 这正是 JMM 设计的精妙之处。
不复杂但容易忽略。

















