volatile写操作通过happens-before传递性“拖带”前面普通变量写,需同时满足:写线程中普通变量赋值在volatile写前、读线程先读到该volatile值、无破坏链路的操作。

volatile 写操作如何“拖带”前面的普通变量写?
关键不在 volatile 本身“广播”了什么,而在于 happens-before 的传递性强制建立了可见性链。只要满足规则,JVM 就必须确保读线程看到写线程在 volatile 写之前对普通变量的所有修改。
必须同时满足的三个条件
仅声明一个 volatile 变量远远不够,要让前面的普通变量写对读线程可见,需同时满足:
- 写线程中,普通变量赋值语句必须出现在
volatile写操作之前(程序顺序规则) - 读线程中,必须先读取该
volatile变量,且读到的是写线程写入的值(volatile变量规则) - 两个线程之间没有其他破坏链路的操作(比如中间插入非同步的共享变量读写)
为什么不是所有普通变量都自动可见?
因为 happens-before 不是全局广播,它只在满足规则的特定操作之间建立单向依赖。常见误区包括:
- 误以为
volatile字段声明后,它所在对象的**所有字段**都自动具备可见性 —— 实际上只有被明确写在它之前的、且未被重排序移出临界区的那些普通变量才受惠 - 忽略编译器/JIT 的重排序:若没有
volatile写作为“内存屏障锚点”,前面的普通变量写可能被移到它之后,从而断开happens-before链 - 读线程没真正读到那个
volatile值(比如用while(!flag)但 flag 从未被写入),则整个链不触发
一个典型失效场景示例
下面这段代码中,x 的写操作**不一定**对读线程可见:
class BadExample {
int x = 0;
volatile boolean ready = false;
void writer() {
x = 42;
// 这里如果发生指令重排序,x=42 可能被延后执行
ready = true; // volatile 写
}
void reader() {
if (ready) { // volatile 读
// 此时 x == 42 不保证成立
}
}
}
真正可靠的做法是确保写操作严格按序,且 JVM 无法合法重排序 —— 这正是 volatile 写提供的语义边界。实际编码中,别依赖“看起来顺序对”,要靠规则链本身说话。










