内存可见性指线程对共享变量的修改能否被其他线程及时看到,因JMM中线程操作变量需经工作内存与主内存间复制,无同步时修改可能不及时刷回主内存;volatile通过强制读写主内存及禁止重排序解决该问题,但不保证原子性;synchronized也保证可见性,但以加锁和更大开销为代价,提供原子性保障。

内存可见性,指的是一个线程对共享变量的修改,能被其他线程“及时看到”。它不是“会不会被看到”,而是“什么时候被看到”——不加同步时,flag = true 执行完,另一个线程仍可能永远读到 false。
为什么改了值,另一个线程却读不到?
Java 内存模型(JMM)把内存分成两层:主内存(所有线程共享)和每个线程私有的工作内存。线程操作变量时,并不直接读写主内存,而是:
- 从主内存把变量副本加载进自己的工作内存
- 在工作内存中读/写
- 再择机把修改刷回主内存(时机不确定!)
问题就出在第三步:没有同步机制时,JIT 编译器或 CPU 缓存可能让线程一直用自己工作内存里的旧值,根本不查主内存。比如下面这段代码会死循环:
public class VisibilityDemo {
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (run) { } // 空循环
System.out.println("退出");
});
t.start();
Thread.sleep(100);
run = false; // 主线程改了,但 t 线程看不到
}
}
volatile 是怎么解决可见性的?
volatile 不是锁,但它强制两点:
立即学习“Java免费学习笔记(深入)”;
- 写
volatile变量时,必须立刻刷新到主内存 - 读
volatile变量时,必须从主内存重新加载,禁止使用工作内存缓存 - 同时禁止编译器和处理器对该变量的读写做重排序(保证有序性)
只需把上面的 static boolean run = true 改成 static volatile boolean run = true,问题就消失。但注意:volatile 只保可见性和有序性,不保原子性——count++ 这种复合操作仍需 synchronized 或 AtomicInteger。
synchronized 也能解决可见性,但它和 volatile 有什么区别?
是的,synchronized 同样具备可见性语义:
- 解锁前,自动把工作内存中所有共享变量刷新到主内存
- 加锁时,强制清空工作内存中对应变量的副本,下次读必须从主内存取
区别在于粒度和开销:
-
volatile是轻量级、无锁、只作用于单个变量 -
synchronized是重量级、有锁、作用于代码块或方法,附带互斥(原子性)保障 - 如果只是“通知状态变更”(如开关标志),优先用
volatile;如果涉及“读-改-写”逻辑(如计数、状态流转),必须用synchronized或原子类
最容易被忽略的一点:可见性问题往往在高并发下不暴露,而是在低负载、长时间运行或 JIT 优化后才突然复现——所以不能靠“测试没出错”来判断是否安全。










