volatile保证可见性的本质是强制读写直连主内存并插入内存屏障,解决因cpu缓存不一致、编译器优化及jmm工作内存副本导致的线程间“看不见更新”问题;它仅保障可见性与有序性,不保证原子性。

volatile 保证可见性的内存本质,核心在于绕过线程本地缓存、直连主内存,并借助硬件级内存屏障强制同步。
为什么普通变量会“看不见更新”
Java 内存模型(JMM)规定:每个线程拥有独立的工作内存(对应 CPU 缓存),变量读写默认操作的是本地副本。主内存是共享的,但线程之间不直接通信。
例如:boolean running = true,线程 A 将其设为 false,可能只更新了自己 L1 缓存中的值;线程 B 仍在自己的缓存中读到旧值 true,导致 while(running) 死循环不退出。
这背后有三重原因:
- CPU 多级缓存(L1/L2/L3)导致各核缓存副本不一致
- JVM 或 JIT 编译器可能将
running当作常量优化(如只加载一次) - 写操作未强制刷回主内存,读操作未强制重载最新值
volatile 怎么强制“每次都是最新”
声明 private volatile boolean running,相当于告诉 JVM:“这个变量,读写必须走主内存”。JVM 在字节码层面插入内存屏障(Memory Barrier),协同硬件协议实现同步:
-
写 volatile 变量时:插入
StoreStore+StoreLoad屏障,确保新值立即写入主内存,并触发 MESI 协议使其他 CPU 缓存中该变量对应的缓存行失效 -
读 volatile 变量时:插入
LoadLoad屏障,清空当前线程工作内存中该变量的缓存副本,强制从主内存重新加载
效果上,它不锁线程、不阻塞调度,但代价是每次读写都绕过高速缓存,直访主内存——这是用时间换一致性。
它能做什么、不能做什么
volatile 解决的是“值变了,别人能不能立刻看见”,不是“这个动作能不能完整做完”。
能可靠支持的场景(前提是操作本身原子):
- 状态标志:如
volatile boolean shutdownRequested - 初始化完成通知:如双重检查单例中的
volatile Singleton instance - 轻量开关:如
volatile int status(仅赋值,不参与++、--等复合运算)
不能解决的问题:
-
volatile int count; count++:读-改-写三步非原子,仍会丢失更新 -
if (flag) doSomething():flag 的读与doSomething()执行之间无原子性保障 - 对象引用虽可见,但其内部字段修改仍不可见(需内部字段也加 volatile 或其他同步)
和 synchronized 的本质区别
synchronized 提供的是互斥 + 可见性 + 原子性:进入临界区前刷新全部变量,退出时回写全部变量,且同一时刻只允许一个线程执行。
volatile 提供的是仅可见性 + 禁止重排序:无锁、无上下文切换、无阻塞,但对读写操作不做任何串行化约束。
一句话区分:要“保护一段逻辑不被并发干扰”,用 synchronized;只要“让一个开关状态及时广播出去”,volatile 更轻量、更合适。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











