在多线程并发执行的环境中,每个线程都有自己的工作内存,线程操作共享变量时会先从主内存拷贝副本到工作内存,操作完成后再写回主内存,这个机制就可能导致不同线程之间无法及时感知共享变量的最新值,也就是可见性问题。volatile关键字就是Java提供的一种轻量级的同步机制,专门用于解决这类可见性问题。

多线程可见性问题的产生原因
Java内存模型(JMM)规定所有共享变量都存储在主内存中,每个线程有自己的工作内存,工作内存保存了线程使用到的变量的主内存副本。线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存的变量。当线程A修改了共享变量的值,还没来得及写回主内存时,线程B从主内存读取的还是旧值,这就产生了可见性问题。
我们可以通过一个简单的示例来复现这个问题:
public class VisibilityTest {
// 未使用volatile修饰的共享变量
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
// 线程A修改flag的值
Thread threadA = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("线程A将flag修改为true");
});
// 线程B循环判断flag的值
Thread threadB = new Thread(() -> {
while (!flag) {
// 空循环,等待flag变为true
}
System.out.println("线程B感知到flag变为true,退出循环");
});
threadB.start();
Thread.sleep(100);
threadA.start();
}
}
运行上述代码会发现,线程A修改flag为true之后,线程B可能一直无法退出循环,就是因为线程B无法及时感知到flag的最新值。
volatile关键字的可见性保证原理
volatile关键字通过以下三个层面的机制保证多线程间的变量可见性:
1. 基于JMM的变量读写规则
当一个变量被volatile修饰后,线程对它的写操作会立即刷新到主内存中,同时会让其他线程工作内存中该变量的副本失效。其他线程再次读取这个变量时,必须从主内存重新加载最新的值,这样就能保证所有线程看到的都是变量的最新状态。
2. 内存屏障的插入
volatile的底层实现依赖于内存屏障(Memory Barrier),JMM会在volatile变量的读写操作前后插入特定的内存屏障:
- 在volatile写操作前插入StoreStore屏障,保证写操作之前的所有普通写操作都已经刷新到主内存
- 在volatile写操作后插入StoreLoad屏障,保证写操作的结果对其他线程的读操作可见
- 在volatile读操作后插入LoadLoad屏障和LoadStore屏障,保证读操作之后的普通读写操作不会重排序到读操作之前,且能读到最新的值
3. happens_before规则的支持
JMM的happens_before规则中明确规定,对一个volatile变量的写操作happens_before后续对这个volatile变量的读操作。也就是说,线程A对volatile变量的写操作的所有结果,对之后线程B读取这个volatile变量的操作都是可见的,这从规则层面保证了可见性。
volatile保证可见性的代码示例
我们修改之前的示例,给flag变量加上volatile修饰:
public class VolatileVisibilityTest {
// 使用volatile修饰共享变量
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
// 线程A修改flag的值
Thread threadA = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("线程A将flag修改为true");
});
// 线程B循环判断flag的值
Thread threadB = new Thread(() -> {
while (!flag) {
// 空循环,等待flag变为true
}
System.out.println("线程B感知到flag变为true,退出循环");
});
threadB.start();
Thread.sleep(100);
threadA.start();
}
}
运行修改后的代码,线程A修改flag为true之后,线程B会立刻感知到变化并退出循环,这就是volatile保证可见性的实际效果。
volatile的使用注意事项
虽然volatile可以保证可见性,但它无法保证原子性,对于复合操作比如i++这类操作,即使变量用volatile修饰,在多线程下还是会出现问题,这类场景需要配合synchronized或者Lock等同步机制使用。另外volatile适合用来修饰状态标记量、双重检查锁定的单例模式中的实例变量等场景,不要滥用。
volatile多线程可见性JMM内存屏障happens_before修改时间:2026-06-20 05:33:28