volatile 不能修复 long/double 的非原子性,而是强制 jvm 保证单次读写原子性与可见性;它通过内存屏障禁止重排序,但不保障复合操作原子性;现代 jdk 中普通 long/double 读写已天然原子,volatile 主要用于可见性与有序性保障。

volatile 在 64 位 long 和 double 类型上的作用,不是“修复”非原子性,而是明确要求 JVM 保证单次读写操作的原子性与可见性。它不改变语言规范的宽松约定,但强制执行更强的语义保障。
volatile 如何解决 long/double 的潜在撕裂问题
Java 内存模型(JMM)允许 JVM 对未修饰的 long/double 进行非原子处理——即把一次 64 位写拆成两次 32 位写。这在理论层面可能导致“撕裂读”(torn read),比如读到高 32 位是旧值、低 32 位是新值。
而声明为 volatile 后:
- JVM 必须确保对该变量的每次读或每次写,都是不可分割的原子操作(JMM 明确要求)
- 同时插入内存屏障,禁止编译器和处理器对相关读写重排序
- 所有线程都能立即看到最新写入的完整值,不会因缓存不一致而读到过期或半更新状态
volatile 并不能让复合操作变原子
它只保障单读或单写原子,不覆盖操作本身的逻辑结构。例如:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
-
counter = 100L→ 原子赋值(volatile 有效) -
counter++→ 非原子:先读、再加、再写,三步间可能被其他线程打断 -
counter += 5→ 同样是非原子的复合操作,volatile 无法防护
这类场景必须用 AtomicLong、synchronized 或 Lock 等手段保障整体原子性。
现实中你大概率不需要靠 volatile 来保 long/double 原子性
主流 JDK(HotSpot)自 Java 5 起,在 64 位 JVM 上天然以原子方式处理 long/double 读写;32 位 JVM 在现代 OpenJDK 中也通过底层指令(如 CAS 或锁总线)规避了撕裂风险。
也就是说:
- 如果你用的是近十年发布的标准 JDK(如 OpenJDK 8u292+、11、17、21),long/double 普通读写已是事实原子
- volatile 的真实价值,在于可见性 + 禁止重排序,比如用作运行开关:
volatile boolean isRunning = true; - 仅当目标环境明确限定为老旧 32 位 JVM(如嵌入式设备、遗留系统),且变量需跨线程频繁读写时,volatile 才承担起“兜底原子性”的角色
替代方案与选型建议
根据实际需求选择更合适的机制:
- 只需保证最新值可见 →
volatile long轻量高效 - 需要自增、比较并交换等原子计算 →
AtomicLong/AtomicDouble - 涉及多个字段协同更新(如金额+时间戳)→
synchronized或ReentrantLock - 想彻底禁用非原子访问(极少数调试/兼容场景)→ JDK9+ 可尝试
-XX:+AlwaysAtomicAccesses参数(仅对 64 位 JVM 生效)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










