volatile无法保证原子性,因其仅作用于单次读/写操作,而count++等复合操作会被jvm拆分为读取、计算、写入三步,中间无锁导致线程切换时数据丢失。

volatile 无法保证原子性,根本原因在于它只作用于单次读或单次写操作,而像 count++ 这类复合操作,在 JVM 层面会被拆解为三个独立步骤:读取(get)、计算(add)、写入(put)。这三个步骤之间没有锁定机制,线程可能在任意一步被切换,导致中间状态被其他线程覆盖。
数据丢失发生在“读-改-写”之间的空隙
多个线程并发执行 count++ 时,典型丢失场景如下:
- 线程 A 从主内存读取
count = 100(volatile 保证这一步拿到最新值) - 线程 B 同样读取到
count = 100(因为 A 还没来得及写回) - A 执行加 1 得到 101,并写回主内存(volatile 保证该写立即可见)
- B 也执行加 1 得到 101,并写回主内存——覆盖了 A 的结果,最终只 +1 而非 +2
volatile 的有序性保障不覆盖“读之前”的依赖链
volatile 提供的 happens-before 规则仅约束:对 volatile 变量的写操作 → 后续对该变量的读操作。但它完全不约束:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 写操作发生前的读操作(比如
count++中第一步的读) - 读操作之后的计算与写入(即“改”和“写”这两步)
- 不同线程对同一变量的多个非原子操作之间的时序协调
CPU 缓存与指令执行的物理现实限制
即使 volatile 强制刷新缓存、插入内存屏障,也无法改变硬件执行的本质:
- CPU 每次只能执行一条指令,
count++必然跨多条指令 - 线程调度由操作系统控制,可在任意指令间隙切换上下文
- volatile 不会阻止线程在“读完值但还没写回去”这段时间内被挂起
所以数据丢失不是 volatile 失效,而是它本就不负责这部分——它解决的是可见性和重排序,不是临界区保护。要保证原子性,必须用 synchronized、Lock 或 AtomicInteger 等真正提供原子语义的机制。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










