volatile通过强制读写主内存和内存屏障保证可见性,但不保证原子性与有序性,无法替代synchronized实现线程安全计数器。

volatile 怎么保证变量修改对其他线程立即可见
Java 中 volatile 关键字不加锁,但能强制线程每次读取都从主内存加载,每次写入都立刻刷回主内存。它绕过了 CPU 缓存(如 L1/L2)和寄存器缓存,直接走主存通路,从而避免一个线程改了值、另一个线程还在用旧缓存副本的情况。
典型场景是状态标志位:running 控制循环是否退出。若不用 volatile,JIT 可能将其优化为“本地寄存器常量”,导致 while 循环永远不退出。
- 仅对单个变量生效,不能保证复合操作(如
i++)的原子性 - 写操作会触发 StoreStore 和 StoreLoad 内存屏障,防止指令重排序影响可见性
- 读操作插入 LoadLoad 和 LoadStore 屏障,确保后续读/写不会被提前到 volatile 读之前
哪些情况用 volatile 就够了,哪些一定不行
volatile 适合“一写多读”且写操作本身是原子的场景,比如开关控制、初始化完成标记、简单数值状态更新(前提是不依赖当前值)。
以下情况它完全无效:
立即学习“Java免费学习笔记(深入)”;
-
counter++:读-改-写三步非原子,即使counter是volatile,仍可能丢失更新 - 多个 volatile 变量之间存在逻辑依赖(如先设
ready = true再设data = 42),无法保证其他线程看到二者顺序一致 - 需要 CAS 或条件等待(如 wait/notify)时,必须配合
synchronized或java.util.concurrent工具类
volatile 和 synchronized 在可见性上的本质区别
synchronized 的可见性保障来自“锁释放时将工作内存全部刷新到主内存,锁获取时清空本地工作内存并从主内存重读”。它作用于整个临界区,天然包含原子性和有序性。
volatile 则轻量得多,只针对单个变量,不阻塞线程,也不提供互斥——它解决的是“能不能看到最新值”,而不是“能不能同时改”。
- 性能上,
volatile读几乎无开销;写比普通变量略重(因内存屏障),但远低于锁竞争 - JVM 对
volatile字段的优化非常有限:禁止指令重排序、禁止寄存器缓存、禁止常见编译器优化(如公共子表达式消除) - 注意:在 Java 5 之前,
volatile不保证有序性(如 double/long 的 64 位写可能被拆成两个 32 位写),现在已修复
为什么 volatile 不能替代 synchronized 实现线程安全计数器
看这个例子:volatile int count = 0;,然后 100 个线程各执行 100 次 count++。最终结果大概率小于 10000。
原因在于:每次 count++ 实际分三步——读 count 值 → 加 1 → 写回。即使每一步的读/写都经过主内存,两个线程仍可能同时读到相同旧值(比如都是 99),各自加 1 后都写回 100,导致一次更新丢失。
- 要真正线程安全,要么用
AtomicInteger.incrementAndGet()(底层 CAS + volatile) - 要么用
synchronized(this) { count++; } - 要么用
ReentrantLock包裹
volatile 本身不提供“比较并交换”或“临界区排他”能力,这是它最常被误用的地方。









