volatile不能保证long/double读写原子性,因JMM仅要求其可见性与有序性,不禁止32位JVM将其拆为两次32位操作;安全方案应选用AtomicLong或synchronized。

volatile 不能保证 long(或 double)变量读写操作的原子性,这是 Java 内存模型中一个容易被忽视的关键陷阱。即使你用 volatile 修饰了 long 类型字段,对它的赋值或读取仍可能被拆分为两个 32 位操作,在 32 位 JVM 或某些硬件平台上导致“半个值”问题——即线程看到的是旧高位+新低位(或反之),造成数据错乱。
volatile 对 long 的原子性承诺有限
Java 规范明确指出:volatile 只能保证变量的“可见性”和“有序性”,不保证 64 位变量(long、double)在所有平台上的读写原子性。虽然现代 64 位 JVM(如 HotSpot)通常会将 long 读写作为单条指令执行(天然原子),但这属于 JVM 实现优化,不是 JMM 的强制要求。JMM 只保证:如果变量是 volatile,那么对其的每次读/写都直接作用于主内存,且禁止重排序——但不禁止把一次 long 写拆成两次 32 位写。
- 在 32 位 JVM 上,
long x = 0x1234567890ABCDEFL;可能被编译为两条 store 指令,中间插入其他线程的写入,导致读到类似0x1234567800000000L或0x0000000090ABCDEFL的“撕裂值” - 即使在 64 位 JVM,若字段未对齐(如因对象布局偏移),也可能触发非原子访问(极少见,但理论上存在)
真正安全的替代方案
要确保 long 变量在并发环境下的读写原子性,必须使用显式同步机制:
-
用
AtomicLong:底层通过 CAS(Compare-and-Swap)或锁机制保证原子性,支持get()、set()、incrementAndGet()等全部原子操作,是首选方案 -
加
synchronized块:对读/写该变量的方法或代码段加锁,简单可靠,适合已有同步边界或需复合操作的场景 -
用
final+ 不可变设计:若变量只初始化一次,后续只读,则声明为final long,由 JMM 保证初始化完成后的安全发布
什么时候 volatile long 可以“凑合用”?
仅当同时满足以下条件时,volatile long 才可能实际安全(但仍不推荐依赖):
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- 目标运行环境确定是 64 位 JVM(如 OpenJDK HotSpot 64-bit)
- 不关心“写入过程中的瞬时撕裂”,只关注最终结果是否一致(例如状态标志位,且高低 32 位语义独立)
- 没有复合操作(如先读后写、自增等),仅做单纯读或单纯写
即便如此,这种做法缺乏可移植性和可维护性,容易误导后续开发者——看起来用了 volatile 就“线程安全”,实则埋下隐患。
验证你的 JVM 行为(不建议用于生产判断)
可通过 Unsafe 或字节码反编译观察 long 字段读写是否生成单条指令(如 lstore/lload),但这只是当前实现细节。正确做法是:不假设平台行为,按 JMM 规范编码——把原子性当作需要显式保障的契约,而不是靠 JVM “大概率靠谱”来赌。

















