volatile不能保证i++原子性,因其仅确保单次读写可见性,而i++包含读-改-写三步且可能被中断;需synchronized或atomicinteger保障复合操作原子性。

Java内存模型(JMM)不是物理内存结构,而是为解决多线程下“变量改了别人看不见”“操作顺序乱了”“++ 一下结果少加了”这三类问题定的一套规则。
为什么 volatile 不能让 i++ 变成原子操作?
很多人以为加了 volatile 就万事大吉,结果发现 counter++ 依然出错——因为 volatile 只管“读写立刻见主存”,不管“读+算+写”这三步是否被其他线程插队。
-
i++实际拆成:从主存读i→ 工作内存加 1 → 写回主存;三步中间任何一步都可能被切换 -
volatile保证每次读都去主存、每次写都刷主存,但不锁住整个流程 - 真正需要原子性时,得用
synchronized或AtomicInteger的incrementAndGet()
什么时候必须用 synchronized 而不是 volatile?
当操作涉及多个变量、或需要“先检查再执行”的复合逻辑时,volatile 失效,必须上锁。
- 比如“懒汉式单例”中双重检查锁定:
if (instance == null) { synchronized {...} }—— 仅靠volatile修饰instance不足以防止指令重排导致的未初始化对象被返回 - 又如银行转账:
accountA.balance -= 100; accountB.balance += 100;,两个字段必须一起成功或一起失败,volatile对它们各自有效,但无法保证整体原子性 -
synchronized不仅同步变量,还建立 happens-before 关系,强制刷新工作内存、禁止跨块重排序
为什么 while(!flag) 会死循环,加 volatile 就好了?
这是最典型的可见性失效场景:线程 B 把 flag 缓存在寄存器或 CPU cache 里,永远不重新加载主存值。
立即学习“Java免费学习笔记(深入)”;
- 没加
volatile时,JIT 可能将while(!flag)优化成类似if (!flag) { while(true) { } },直接跳过后续读取 -
volatile强制每次循环都执行read+load,绕过工作内存缓存 - 注意:它不解决“线程 B 正在循环时,线程 A 修改了 flag 却没触发唤醒”的问题——若需及时响应,还得配合
wait/notify或LockSupport
JMM 的三大特性从来不是孤立起作用的。一个 synchronized 块同时扛起原子性、可见性、有序性;而 volatile 是轻量替代,只在“单变量读写+无依赖逻辑”的窄场景里才真正安全。最容易忽略的是:happens-before 规则才是所有保障的底层契约,不是语法糖,也不是 JVM 的“善意优化”。










