volatile通过强制读写主内存和插入内存屏障保证可见性,适用于状态标志等独立读写场景,但不保证复合操作原子性。

Java 中用 volatile 解决可见性陷阱,核心就一句话:让变量的修改对其他线程“立刻可见”。它不加锁、不阻塞,专治那种“我明明改了,你却看不到”的问题。
为什么普通变量会掉进可见性陷阱
每个线程都有自己的工作内存(比如 CPU 缓存或寄存器),普通变量读写可能只在工作内存里打转。比如主线程把 flag = true 写进了主内存,子线程却一直读自己缓存里的 false,结果死循环卡住——这不是 bug,是 Java 内存模型(JMM)的正常行为。
更隐蔽的是 JIT 编译器优化:空循环 while (!flag) 可能被当成“不会变的常量”直接优化成无限循环,连主内存都不去读。
volatile 怎么破局
加了 volatile,JVM 就会强制:
- 每次读取都绕过工作内存,直接从主内存拿最新值
- 每次写入都立刻刷回主内存,不等后续同步
- 插入内存屏障,禁止编译器和 CPU 对它前后指令乱序(比如防止“先设 ready=true,后赋 data=42”被颠倒)
怎么用才有效
声明时加上 volatile 修饰符,其他代码完全不用动:
典型适用场景包括:
- 状态开关:如
running控制线程启停 - 完成标记:如
finished表示任务结束 - 中断信号:配合
Thread.interrupt()使用
注意:它只保“单次读或写”的可见性,不保复合操作原子性。比如 count++(读-改-写三步)仍会丢更新,这种得用 AtomicInteger 或锁。
什么时候 volatile 不够用
以下情况不能只靠 volatile:
- 需要“读-改-写”原子性,比如计数器自增、余额扣减
- 多个变量之间有依赖顺序,比如必须先写
data再写ready,且要求其他线程看到这两个动作的先后关系 - 要等待条件成立(比如
wait/notify),必须搭配synchronized或Lock
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











