volatile通过内存屏障强制读写主内存,解决多线程可见性问题;但不保证原子性,仅适用于状态标志等简单场景。

volatile 不能靠“猜”,它靠的是 JVM 对内存模型的强制约定:写就刷主存,读就绕缓存。
为什么普通变量在多线程中“看不到更新”
每个线程有自己的工作内存(通常是 CPU 缓存),读写变量时默认操作的是本地副本。主线程改了 running,但子线程可能一直从自己缓存里读旧值——这不是 bug,是 JMM 的默认行为。没有同步机制时,这种“信息孤岛”完全合法。
常见错误现象:while(running) { ... } 死循环不退出,即使主线程已把 running = false 写入主内存。
- 根本原因不是线程没执行,而是它压根没去主内存重新加载
running - 编译器甚至可能把
running优化成常量(比如全程只读一次),导致循环永不终止
volatile 怎么打破缓存隔离
JVM 对 volatile 变量的读写插入了内存屏障(Memory Barrier):
- 写操作后强制刷新到主内存(
volatile boolean flag = true→ 立即可见) - 读操作前强制清空本地缓存,直接从主内存加载(
if(flag)→ 每次都取最新) - 这个过程不依赖锁,也不阻塞线程,开销远低于
synchronized
注意:它不改变变量本身,只改变 JVM 对该变量的内存访问策略。你声明 private static volatile boolean flag,就是在告诉 JVM:“每次读写这个变量,都给我走主内存。”
volatile 保证可见性,但不等于“线程安全”
可见性 ≠ 原子性。这是最容易踩的坑。
-
flag = true是原子的,volatile能保证它被立即看到 -
counter++不是原子的(读-改-写三步),即使counter是volatile,多个线程仍可能丢失更新 - 复合逻辑如
if (flag) doSomething();中,flag的读取和doSomething()的执行之间没有原子性保障
所以它适合做状态标志(启动/停止/完成)、轻量级信号量,不适合计数、状态转换链或需要临界区保护的操作。
什么时候必须用 volatile,而不是 synchronized
当只需要“一个线程改,其他线程立刻感知”,且不涉及读-改-写这类复合操作时,volatile 是更轻量的选择。
- 单例模式双重检查锁定中的
instance字段必须volatile,否则可能返回未完全构造的对象 - 中断标志
Thread.interrupted()底层依赖类似语义,自己实现类似逻辑时也应如此 - 避免为简单布尔开关加锁——锁的开销和复杂度远超必要
真正容易被忽略的是:volatile 修饰的引用类型,只保证引用本身的可见性,不保证其指向对象内部字段的可见性。比如 volatile List,list 引用变了别人能看到,但 list.add() 之后的内容是否可见,得看 list 实现类自身是否线程安全。










