volatile 是 java 中用于保证变量可见性和禁止指令重排序的轻量级同步机制,它与 jmm(java 内存模型)的主内存和工作内存的关系在于:jmm 规定所有变量都存储在主内存中,每个线程拥有独立的工作内存;当线程读写 volatile 变量时,会强制从主内存重新读取或立即刷新到主内存,从而绕过工作内存的缓存隔离,确保多线程环境下的数据一致性。

JMM 中主内存与工作内存的基本交互流程
在 JMM 抽象模型中,主内存负责存储所有实例变量的值,而工作内存则保存了该变量的一份副本供线程使用。普通变量在线程间通信时,必须通过主内存进行中转,但这个过程并不保证原子性或即时性。对于非 volatile 变量,线程可能长时间持有工作内存中的旧值,导致其他线程无法及时感知变化。这种设计虽然提高了性能,但在并发场景下容易引发数据不一致问题。

public class JmmExample {
// 普通变量,存在可见性问题
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread writer = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
count++; // 操作工作内存副本,不一定立即刷回主内存
}
});
Thread reader = new Thread(() -> {
while (count == 0) {
// 忙等待,可能永远看不到 count 的变化
}
System.out.println("Count: " + count);
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
上述代码展示了普通变量在多线程环境下的典型问题。writer 线程对 count 的修改可能仅停留在其工作内存中,reader 线程由于读取的是自己的工作内存副本,可能始终看到初始值 0。这体现了 JMM 中工作内存与主内存之间缺乏强制同步机制的后果。
立即学习“Java免费学习笔记(深入)”;
volatile 如何改变主内存与工作内存的交互行为
当变量被声明为 volatile 时,JMM 对其访问施加了特殊约束。任何对 volatile 变量的写操作都会立即刷新到主内存,确保其他线程能立即看到最新值。同时,读操作也会强制从主内存重新加载变量值,忽略工作内存中的缓存副本。这种“写即刷新、读即重载”的行为打破了普通变量在工作内存中的隔离性,实现了跨线程的可见性。

public class VolatileExample {
// volatile 变量,保证可见性
private static volatile boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) {
// 循环体
}
System.out.println("Worker stopped.");
});
worker.start();
Thread.sleep(1000); // 模拟业务执行
running = false; // 写操作立即刷入主内存
worker.join();
}
}
在这个例子中,main 线程将 running 设置为 false 后,worker 线程能够立即感知并退出循环。这是因为 volatile 保证了写操作的即时可见性,避免了因工作内存缓存导致的死锁或无限等待。需要注意的是,volatile 不保证原子性,例如 count++ 这样的复合操作仍然需要 synchronized 或 Atomic 类来保障。
基于程序中 volatile 变量写入后的具体结果验证
在实际程序中,volatile 变量的写入操作不仅影响可见性,还引入了内存屏障(Memory Barrier)。JMM 规定,在 volatile 写操作之前,所有之前的读写操作都不能被重排序到写操作之后;在 volatile 读操作之后,所有之后的读写操作都不能被重排序到读操作之前。这种指令重排序的限制,使得 volatile 在某些场景下可以替代部分 synchronized 的功能,特别是在状态标志位的控制中。
为了验证这一行为,我们可以观察一个包含指令重排序风险的示例。如果没有 volatile,编译器或处理器可能会优化代码顺序,导致逻辑错误。而加上 volatile 后,这种优化被禁止,确保了程序语义的正确性。例如,在单例模式的双重检查锁定(DCL)中,instance 变量必须声明为 volatile,以防止指令重排序导致其他线程获取未完全初始化的对象。


















