登录
首页 >  文章 >  java教程

int与long在JVM中的原子性区别

时间:2026-05-30 09:22:07 301浏览 收藏

这篇文章深入剖析了 int 和 long 类型在 JVM 中原子性行为的本质差异:int 因固定 32 位宽度,在所有 JVM 上均能通过单条硬件指令完成读写,天然具备原子性;而 long 的 64 位特性使其在 32 位 JVM 中必然被拆分为两次非原子的 32 位操作,即便在 64 位 JVM 中虽常编译为单条指令(如 movq),却仍不被 JVM 规范强制保证原子性——真正决定原子性的,是底层字节码如何映射为汇编、内存对齐是否满足、以及硬件指令能否一次性覆盖全部 64 比特,而非 Java 代码表面的简洁。想避开多线程下的诡异数据撕裂?别只信文档,亲手用 javap 和 PrintAssembly 下钻到字节码与汇编层,亲眼见证“一锤定音”与“两锤分家”的物理真相。

如何通过字节码分析掌握 int 与 long 在 32 位与 64 位 JVM 底层原子性读写特性的物理差异

要真正看清 int 和 long 在不同 JVM 上的原子性差异,不能只看 Java 代码表现,得下到字节码和硬件执行层去看——核心在于“一次内存操作能否完整覆盖变量全部比特”。

int 的读写在所有 JVM 上天然原子

int 占 4 字节(32 位),无论 32 位还是 64 位 JVM,底层指令(如 x86 的 movl、ARM 的 str)都能单条完成整数的加载或存储。JVM 规范明确要求 int 赋值是原子操作,对应字节码 istore/iload 在运行期会映射为单次 CPU 内存访问。

反编译验证方式:

  • 写一个 int i = 123; 方法,用 javap -c 查看,会看到清晰的 iconst_123istore_1 流程
  • 该 store 指令在 HotSpot 中最终调用的是平台相关的原子写入函数(如 OrderAccess::store),对 32 位整数无需额外屏障

long 在 32 位 JVM 中被拆成两次 32 位操作

32 位 JVM(如 32 位 HotSpot)无法用一条指令处理 64 位数据。它把一个 long 拆成高 32 位和低 32 位,分别用两条独立的 32 位指令读写。字节码层面仍是 lstore/lload,但 JIT 编译后生成的汇编会暴露本质:

  • lstore_0 可能编译为两条 movl:先存低 32 位到地址 X,再存高 32 位到地址 X+4
  • 若线程 A 正存低 32 位,线程 B 插入并存自己的高 32 位,再线程 A 存高 32 位——结果就是高低混搭的非法值
  • 这种非原子性在 javap 看不到,必须用 -XX:+PrintAssembly(需 hsdis)观察生成的汇编

64 位 JVM 对 long 的优化与实际行为

64 位 JVM 具备原生 64 位寄存器和指令(如 x86-64 的 movq),理论上可单指令完成 long 读写。但是否真做原子处理,取决于具体实现:

  • HotSpot 在 64 位模式下默认将 lstore/lload 编译为单条 movq,前提是目标地址 8 字节对齐(JVM 通常保证)
  • 即使硬件支持,JVM 规范仍允许不保证原子性——所以不能依赖,尤其在跨平台或嵌入式 JVM 场景
  • volatile long 可强制插入内存屏障,并确保编译为带 lock 前缀(x86)或 dmb 指令(ARM),从语义上锁定整个 64 位操作

动手验证的关键步骤

不靠猜测,靠证据:

  • System.getProperty("sun.arch.data.model") 确认当前 JVM 位数
  • 写最小复现类(含两个线程交替写 1L/-1L 到 static long 字段),加上循环读校验
  • 加 JVM 参数:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly,过滤出相关方法汇编
  • 对比 32 位 JVM 输出中是否有连续两条 movl,64 位中是否出现 movqlock xchgq

物理差异就在这里:int 是“一锤定音”,long 在 32 位上是“两锤分家”,在 64 位上是“一锤但未必锁门”——原子性不是自动赠送的,而是由指令宽度、对齐、内存模型共同决定的。

以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>