volatile解决多线程内存可见性问题,不保证原子性与互斥;通过禁止重排序、强制写回主存及缓存失效实现;适用于独立布尔标志或单例引用等场景。

volatile 解决的是多线程下的内存可见性问题
它不保证原子性,也不提供互斥锁功能,只确保一个线程对 volatile 变量的修改能立即被其他线程看到。这是 JVM 通过禁止指令重排序 + 强制写回主存 + 使其他 CPU 缓存失效(基于 MESI 协议)实现的。
常见误用场景:用 volatile int count 做自增(count++),结果依然可能丢失更新——因为读-改-写是三个步骤,volatile 只保障每次“读”拿到最新值、“写”立刻刷出,但中间的计算过程不被保护。
volatile 不能替代 synchronized 的典型情况
当需要以下任一能力时,volatile 就不够用了:
- 复合操作的原子性(如
if (flag) { doSomething(); flag = false; }) - 临界区保护(多个变量需保持一致状态,比如
size和elements[]) - 防止指令重排序影响逻辑(虽然
volatile有 happens-before,但仅限于对该变量自身的读写)
例如双检锁单例中,instance 必须声明为 volatile,否则可能因重排序导致其他线程看到未初始化完成的对象;但构造函数内部的字段赋值仍需靠 volatile 的写屏障来约束顺序,不是靠它“加锁”。
立即学习“Java免费学习笔记(深入)”;
volatile 变量的读写性能开销在哪
相比普通变量,volatile 读写会触发内存屏障(Memory Barrier):
-
volatile写:插入 StoreStore + StoreLoad 屏障,强制刷新到主存,并使其他核心缓存行失效 -
volatile读:插入 LoadLoad + LoadStore 屏障,强制从主存或最新缓存行重新加载
在 x86 上,volatile 写的 StoreLoad 屏障开销最大(相当于 mfence),而读的开销接近普通读。频繁读写 volatile 变量(如轮询标志位)可能成为瓶颈,此时应考虑 LockSupport.parkNanos() 或更高级的并发工具。
哪些变量适合用 volatile 声明
满足以下全部条件时,volatile 才是合适选择:
- 变量是基本类型(
boolean、int、long等)或对象引用(注意:对象内部字段不自动具备可见性) - 写操作不依赖当前值(即无
x++、x += 1、if (x == 1) x = 2这类读-改-写) - 变量独立存在,不参与不变性约束(如与另一个变量共同构成某个状态)
典型例子:private volatile boolean shutdownRequested;、private static volatile Singleton instance;。一旦涉及状态流转或组合判断,就该换 AtomicBoolean 或锁了。










