Java内存模型对原子性的保证是有条件的:除long和double外,所有基本类型读写操作默认原子;32位JVM中,long/double可能被拆为两次32位操作导致非原子,volatile可保证其读写原子性,但复合操作仍需AtomicLong或synchronized。

Java内存模型(JMM)对原子性的保证是有条件的:除 long 和 double 外,所有基本类型(如 int、boolean、char 等)的读写操作在任意 JVM 上都默认原子。但这两个 64 位类型在 32 位 JVM 中可能被拆成两次 32 位操作,导致写入非原子——你看到的值,可能是线程 A 的高 32 位和线程 B 的低 32 位拼出来的“半新半旧”数据。
为什么 32 位 JVM 会拆分 long/double 写入
32 位 CPU 寄存器宽度为 32 位,一次最多处理 32 位数据。JVM 在这类平台上通常将一个 64 位 long 或 double 的写入,分解为两个独立的 32 位写操作:先写高位(high 32 bits),再写低位(low 32 bits)。如果两个线程并发写入不同值,执行顺序交错,就可能产生非法组合。
- 线程1写 -1(全 1 的 64 位:0xFFFFFFFFFFFFFFFF)→ 先写高位 0xFFFFFFFF
- 线程2写 0(全 0 的 64 位:0x0000000000000000)→ 先写高位 0x00000000
- 线程2再写低位 0x00000000
- 线程1再写低位 0xFFFFFFFF
- 最终内存中留下:高位 0x00000000 + 低位 0xFFFFFFFF = 0x00000000FFFFFFFF(即十进制 4294967295)
volatile 能解决这个问题吗
能,但仅限于读和写单个值本身。JMM 明确规定:声明为 volatile 的 long/double 变量,其 get 和 set 操作是原子的。这意味着即使在 32 位 JVM 上,volatile 会强制 JVM 使用同步机制(如内存屏障 + 锁总线或 CAS 尝试)来确保整个 64 位值一次性读/写完成。
- 它不提供复合操作(如 i++、+=)的原子性,这些仍需 AtomicLong 或 synchronized
- 它不改变底层硬件限制,而是通过 JVM 层语义约束实现原子效果
- 是否生效取决于 JVM 实现,但主流 JDK(如 HotSpot)在 32 位模式下对 volatile long/double 确实按原子方式处理
如何确认当前运行环境是否受影响
不能只看操作系统位数,而要看 JVM 运行时架构。最可靠方式是检查系统属性:
System.getProperty("sun.arch.data.model") —— 返回 "32" 或 "64"
- 返回 "64":现代 JVM 默认用单条指令读写 long/double,天然原子,无需额外防护
- 返回 "32":存在非原子风险,尤其当变量未声明为 volatile 且被多线程并发写入时
- 注意:-XX:+UseCompressedOops 等选项不影响该判断,它只影响对象指针压缩,不改变基本类型原子性规则
更稳妥的实践建议
依赖 volatile 是可行解,但不是最优解。真正面向并发场景,应优先选用明确设计用于原子操作的工具:
- 用 AtomicLong 替代普通 long:它基于 Unsafe.compareAndSwapLong,在所有平台都保证完整 64 位 CAS 原子性
- 避免裸写 long/double 共享变量,尤其在金融、计数、时间戳等对精度敏感的字段上
- 若必须用普通 long/double,且确定只读不写或写入由单一线程完成,可不加 volatile;否则一律声明 volatile
- 不要假设“现在没出问题=永远安全”,非原子现象具有偶发性和难以复现的特点










