可见性问题本质是线程修改共享变量后,其他线程因工作内存缓存旧值而无法及时看到更新;根源在于jmm将内存分为主内存与线程私有工作内存,且多核cpu缓存异步,导致读写不同步。

多线程下共享变量的可见性问题,本质是“一个线程改了,另一个线程看不到”——不是代码写错了,而是 JVM 和硬件协同工作时,默认不保证这种“实时同步”。
可见性问题怎么产生的?
Java 内存模型(JMM)把内存抽象成两部分:所有线程共享的主内存,和每个线程私有的工作内存(比如 CPU 缓存)。线程不能直接读写主内存,而是先把变量拷贝一份到自己的工作内存里操作。
这就带来两个关键断点:
- 线程 A 修改变量后,不一定立刻把新值写回主内存;
- 线程 B 读变量时,可能一直用自己工作内存里的旧副本,根本不去主内存查最新值。
比如一个 boolean flag = false,线程 A 执行 flag = true 后,线程 B 的 while 循环可能永远卡在 !flag,因为它始终读的是缓存里的 false。
为什么单线程没问题,多线程就出问题?
单线程下,所有操作都在同一个工作内存里,顺序执行,不存在“谁看谁”的问题。但多核 CPU 上,每个核心有自己的缓存,线程可能跑在不同核心上——A 在 Core1 改了变量,B 在 Core2 读,中间隔着 L1/L2 缓存层级,没有自动同步机制,旧值就“卡住”了。
这不是 Java 的 bug,而是现代硬件为性能做的取舍:缓存加速 + 指令重排,在单线程下完全安全,但在多线程共享场景下,就暴露为可见性漏洞。
怎么让修改对其他线程“可见”?
关键不是“禁止缓存”,而是建立明确的同步契约,告诉 JVM 和 CPU:“这里必须刷新”“那里必须重读”。常用手段有:
- volatile:写操作强制刷回主内存,读操作强制从主内存加载,同时插入内存屏障禁止相关指令重排;
- synchronized:解锁前把所有修改同步到主内存,加锁时清空工作内存,迫使线程重新读取最新值;
- final 字段:对象构造完成那一刻,final 字段的值对其他线程天然可见(前提是构造过程中 this 没逸出);
- Lock / 原子类:底层基于 volatile 或 CAS,同样遵循 happens-before 规则,保障可见性。
这些机制不是靠“魔法”,而是通过内存屏障、缓存一致性协议(如 MESI)、以及 JMM 定义的 happens-before 关系,把原本松散的读写行为,锚定到可预测的时序上。
什么情况下不用操心可见性?
局部变量永远安全——它存在线程栈里,不共享,不跨线程;只读的共享变量(比如配置常量)也无需额外同步;还有被正确同步保护的临界区,只要进出锁一致,内部变量自然具备可见性。
真正要警惕的,是那些没加任何同步修饰、又被多个线程“一读一写”的普通成员变量或静态变量——它们就是可见性问题最常藏身的地方。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











