AI生成-JMM(Java Memory Model)总结

JMM(Java Memory Model)总结

一、JMM 是什么

JMM = Java Memory Model(Java 内存模型),是一套规范/规则,定义了多线程环境下,一个线程对共享变量的写入,什么时候对另一个线程可见

JMM 不是一段代码或一个组件,而是跨平台的并发行为规范。

为什么需要 JMM?

没有统一规则时,同一份并发代码在不同 CPU 架构上行为不同(x86 强内存序 vs ARM 弱内存序),程序行为不可预测。JMM 的出现就是为了屏蔽硬件差异,给程序员一个统一的保证。


二、JMM 的三层架构

层级 是什么 举例
规范层 规则定义 happens-before、volatile 语义
API 层 程序员用的关键字 volatilesynchronizedfinal
实现层 JVM 真正生成的指令 内存屏障、lock 前缀、缓存刷新

JMM 规范说"应该怎样",volatile/synchronized 是"怎么做到的",JVM 底层指令是"真正干活的"。


三、JMM 解决的三大核心问题

问题 含义 JMM 提供的规则 对应关键字
可见性 写了对别人不可见 volatile 读从主内存刷;volatile 写刷回主内存 volatile
有序性 指令被编译器/CPU 重排 happens-before 规则,禁止特定重排 volatilesynchronizedfinal
原子性 操作被中途打断 同一时刻只有一个线程执行 synchronized

四、JMM 抽象模型

┌──────────────┐     ┌──────────────┐
│   线程 A     │     │   线程 B     │
│ ┌──────────┐ │     │ ┌──────────┐ │
│ │ 本地内存  │ │     │ │ 本地内存  │ │
│ │ (抽象)   │ │     │ │ (抽象)   │ │
│ └────┬─────┘ │     │ └────┬─────┘ │
└──────┼───────┘     └──────┼───────┘
       │       read/write      │
       └──────────────────────┘
              主内存 (Main Memory)

"本地内存"的物理映射

JMM 的"本地内存"是抽象概念,物理上映射到多个东西:

层级 实际位置 说明
CPU 寄存器 CPU 内部 编译器优化后,变量可能直接放寄存器,对其他线程不可见
L1/L2 缓存 CPU 核心 每个核有自己独立的 L1/L2,L3 是共享的
写缓冲区 (Store Buffer) CPU 核心→L1 之间 CPU 写数据先进 Store Buffer,还没到缓存
失效队列 (Invalidate Queue) CPU 核心 处理缓存一致性协议的延迟队列

L1/L2 缓存只是"本地内存"的一部分,不是全部。


五、happens-before 规则(JMM 的核心)

happens-before 定义的是逻辑上的先后关系,不是时间先后:

如果 A happens-before B,那么 A 的操作结果对 B 可见

6 条天然规则(不需要任何关键字)

规则 含义
程序顺序规则 同一线程内,前面的操作 happens-before 后面的
volatile 写规则 volatile 写 happens-before 后续的 volatile 读
synchronized 规则 unlock happens-before 后续的 lock
传递性 A → B, B → C,则 A → C
start 规则 Thread.start() happens-before 线程内所有操作
join 规则 线程内操作 happens-before Thread.join() 返回

六、volatile 的底层实现

JVM 在编译和运行时通过插入内存屏障实现 volatile 语义:

Java 源码:volatile boolean running;
    ↓
字节码:putvolatile / getvolatile 指令
    ↓
JIT 编译:插入内存屏障
    ├── LoadLoad   — 读→读 之间,确保前面的读完成
    ├── StoreStore  — 写→写 之间,确保前面的写刷出
    ├── LoadStore   — 读→写 之间
    └── StoreLoad   — 写→读 之间(最重的屏障)
    ↓
x86 汇编:lock 前缀 + mfence/sfence/lfence

典型场景:Store Buffer 导致可见性问题

// 线程 A
running = false;  // 写入可能停在 Store Buffer 里,还没进 L1 缓存

// 线程 B
while (running) { ... }  // 读到的可能还是旧值(true)

volatile 的作用:在所有层面插入内存屏障,强制刷新 Store Buffer、处理 Invalidate Queue,保证可见性。


七、JMM 的定位

  程序员视角(JMM 保证)
  ┌─────────────────────────────┐
  │  volatile / synchronized    │  ← 写代码时只需要遵守这些规则
  │  happens-before             │
  └──────────┬──────────────────┘
             │ 屏蔽了下面的差异
  ┌──────────┴──────────────────┐
  │  硬件视角(各不相同)        │
  │  x86: 强内存序,TSO          │  ← 几乎不重排
  │  ARM: 弱内存序,大量重排     │  ← 重排很激进
  │  Store Buffer / 缓存一致性   │
  └─────────────────────────────┘
  • 没有 JMM:任何平台都可能出现并发 bug;x86 因硬件内存序更强,部分 bug 被"碰巧掩盖";ARM 更容易让 bug 显现
  • 有 JMM:只要代码符合 happens-before 规则,所有平台行为一致

重排的两个来源

重排来源 x86 ARM 说明
JIT 编译器重排 ✅ 会发生 ✅ 会发生 跟硬件无关,JVM 层面的优化
CPU 硬件重排 ❌ 几乎不发生 ✅ 大量发生 x86 是 TSO 强内存序,ARM 是弱内存序

同样的有 bug 的代码(没加 volatile),在 x86 上因硬件更强可能"碰巧跑对",掩盖了 bug;在 ARM 上硬件更弱,bug 就暴露出来了。两个平台都会出问题,只是 ARM 更容易暴露。


八、JMM 只解决多线程问题

JMM 存在的唯一意义:当多个线程访问同一块共享数据时,告诉你怎么写才是对的。

单线程不需要 JMM

JMM 解决的三个问题在单线程下都不存在:

问题 多线程 单线程
可见性 我写的你看不到 只有一个线程,自己写的自己当然看得到
有序性 重排后别人看到乱序 JIT/CPU 确实会重排,但保证结果等价于顺序执行
原子性 操作中途被别人打断 没人打断你

关键原理 — as-if-serial:单线程下,编译器和 CPU 可以随意重排,但保证最终结果和顺序执行完全一致。

// 单线程 — 无论怎么重排,c 的结果一定是 3
int a = 1;      // 语句1
int b = 2;      // 语句2
int c = a + b;  // 语句3(依赖1和2,不会被排到前面)

隐式多线程

严格说,Java 里不存在真正的单线程程序:

你的 main 线程
  +
GC 线程
  +
Finalizer 线程(处理 finalize())
  +
Reference Handler 线程

但这些是 JVM 内部的事,JMM 的规则已经保证这些场景的安全,业务代码不需要操心。


九、一句话总结

JMM 是一套跨平台的并发行为规范,它定义了多线程下读写的可见性、有序性、原子性规则,通过 volatile/synchronized/final 等关键字作为 API 接口,由 JVM 在底层通过内存屏障等机制真正落实,屏蔽了不同 CPU/编译器的内存模型差异,实现了"写一次,到处正确运行"的并发保证。JMM 只解决多线程问题,单线程下 as-if-serial 原则保证了正确性,无需关心。

posted @ 2026-06-17 17:30  deyang  阅读(4)  评论(0)    收藏  举报