volatile不能保证复合操作原子性,因其仅保障单次读/写操作的可见性与有序性,而count++等“读—改—写”操作会被jvm拆解为多步指令,线程可在此间交错执行,导致丢失更新。

volatile 不能保证复合操作的原子性,因为它只干预单次读或单次写的行为,而复合操作(比如 count++、value += 1、flag = !flag)本质上是“读—改—写”三个分离步骤,中间存在可被其他线程插入的时间窗口。
它只管“进出”,不管“过程”
volatile 的作用边界非常明确:
- 每次读取都强制从主内存加载最新值(不缓存旧值)
- 每次写入都立即刷回主内存(不滞留在线程本地)
- 在读/写前后插入内存屏障,防止指令重排序
但它对“读完之后怎么算、算完之后怎么写”完全不干预。JVM 会把 count++ 拆成三条独立字节码:getstatic → iadd → putstatic。volatile 只确保第一步和第三步直通主内存,中间那步计算发生在寄存器里,不受任何保护。
典型丢失更新场景
两个线程同时执行 volatile int count = 0; count++,可能这样发生:
- 线程 A 读到 count = 0
- 线程 B 也读到 count = 0
- A 在自己寄存器中加 1,得 1;B 同样得 1
- A 写回 count = 1;B 也写回 count = 1
结果是两次自增只体现为一次,数据丢失——这不是偶然,而是必然发生的竞态条件。
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
它和 synchronized / CAS 的根本区别
对比来看:
- synchronized:把整个代码块变成临界区,其他线程必须排队等待,实现逻辑层面的“不可分割”
-
CAS(如 AtomicInteger):用 CPU 级原子指令(如
lock xadd)把“读—比—换”三步打包成一条不可中断的操作 - volatile:既不排队,也不打包,只是让每次“门开”和“门关”都看得见、不乱序
所以 volatile 能安全用于状态标志(如 running = false)、单次赋值、双重检查锁中的实例引用发布,但凡涉及“不止一次内存访问 + 中间依赖”,就必须换更重的同步机制。
怎么判断一个操作是不是 volatile 能扛住的
简单看这个操作是否满足以下全部条件:
- 仅包含一次读 或 一次写
- 不依赖当前值做计算(比如不基于旧值决定新值)
- 不与其他变量构成逻辑一致性约束(比如不和时间戳、状态码一起更新)
只要有一条不满足,volatile 就不够用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










