理解 JVM 的 8 个原子操作与 `volatile` 的语义

理解 JVM 的 8 个原子操作与 volatile 的语义

在并发编程中,volatile 常常被提及。但要真正理解它的作用,需要先回到 Java 内存模型(JMM) 的基础:线程与主内存之间的交互。JMM 定义了 8 个原子操作 来描述这些交互过程,而 volatile 的语义正是基于它们实现的。


JVM 的 8 个原子操作

JMM 把 主内存工作内存执行引擎 的数据交互拆分成了 8 个最小操作,这些操作是并发语义的基石:

  1. lock(锁定)

    将主内存中的变量标记为某个线程独占。

  2. unlock(解锁)

    解除独占标记,使变量可被其他线程访问。

  3. read(读取)

    从主内存中读取变量值,传输到线程的通道。

  4. load(载入)

    read 到的值写入线程工作内存的变量副本。

  5. use(使用)

    将工作内存中的值传给执行引擎使用。

  6. assign(赋值)

    将执行引擎计算后的值赋给工作内存中的变量副本。

  7. store(存储)

    将工作内存的值传输到通道。

  8. write(写入)

    store 传来的值写入主内存。


四组配对关系

这 8 个操作其实是 四组成对动作

  • lock / unlock:保证变量在某一时刻只有一个线程独占。
  • read / load:把主内存的值带入工作内存。
  • store / write:把工作内存的值写回主内存。
  • use / assign:工作内存与执行引擎之间的数据交互。

示意图:

主内存 <──read/load──> 工作内存 <──use/assign──> 执行引擎

^ │

└─────────────store/write───────────┘

其中 use/assign 仅在 线程内部 发生,对跨线程的可见性影响不大,因此开发者平时不太关心它。


volatile 的语义

volatile 并不会改变 8 个原子操作的存在方式,而是通过 内存屏障 来影响其中部分操作的执行顺序和可见性:

  1. 保证可见性

    • 写入 volatile 变量:强制执行 store → write,立即刷新主内存。
    • 读取 volatile 变量:强制执行 read → load,直接从主内存取值。
  2. 保证有序性

    • volatile 写操作前后,JVM 插入 StoreStore、StoreLoad 屏障,防止指令重排。
    • volatile 读操作后,插入 LoadLoad、LoadStore 屏障,确保读到的值对后续操作可见。
  3. 不保证原子性

    • volatile 只能保证单次读/写的原子性。
    • 对于复合操作(如 count++),依然会分解成多步(read → load → use → assign → store → write),在多线程下可能出现 丢失更新

为什么 use / assign 看似多余?

有人会觉得 use/assign 没有必要,因为我们写代码时从未直接操作过它。实际上:

  • read/load、store/write 关注的是 线程与主内存之间 的数据交换。
  • use/assign 关注的是 线程工作内存与执行引擎之间 的交互。

这部分虽然我们平时感知不到,但在 JMM 的规范 层面,它是必须被定义的,否则无法保证指令级别的精确定义。


总结

  • JVM 的 8 个原子操作分别是:lock / unlock / read / load / use / assign / store / write
  • 它们可以配对为四组:
    • lock/unlock
    • read/load
    • store/write
    • use/assign
  • volatile 主要约束的是 read+load、store+write ,配合内存屏障保证 可见性 + 有序性
  • volatile 不保证复合操作的原子性 ,如果需要保证,需要 CAS

✍️ 记住一句话:
volatile 让读写操作直达主内存,并通过内存屏障保证顺序,但不会把复合操作变成原子操作。

相关推荐
老白干2 小时前
jjwt 0.9.1 在 JDK 11+ 上的两个“坑”与完整解决方案
java·python·log4j
圆山猫8 小时前
[Virtualization](四):Linux KVM/RISC-V 的 vCPU 运行路径
java·linux·risc-v
城管不管9 小时前
ReAct、Plan-and-Execute、Reflection 三大智能 Agent 范式核心区别
java·人工智能·算法·spring·ai·动态规划
IT小白杨9 小时前
从环境制备到自动化工作流:多账号运营的工程化架构拆解
java·经验分享·自动化·安全架构·指纹浏览器
豆瓣鸡9 小时前
算法日记 - Day3
java·开发语言·算法
萧瑟余晖10 小时前
Java深入解析篇九之NIO详解
java·网络·nio
The Chosen One98510 小时前
高进度算法模板速记(待完善)
java·前端·算法
极光代码工作室12 小时前
基于SpringBoot的课程预约系统
java·springboot·web开发·后端开发
Leighteen13 小时前
`try-finally` 里的 `return`:为什么 `finally` 会悄悄改掉返回值、吞掉异常
java·开发语言
名字还没想好☜13 小时前
Go 的 time.After 在 select 循环里内存泄漏:定时器堆积原理与 timer.Reset 正确姿势
java·数据库·golang·go·goroutine