Skip to content
清晨的一缕阳光
返回

Java 内存模型 (JMM) 详解

Java 内存模型 (JMM) 详解

Java 内存模型是并发编程的理论基础,理解 JMM 对编写正确的多线程程序至关重要。

一、为什么需要 JMM

1.1 硬件内存架构

CPU → 缓存 (L1/L2/L3) → 主内存

多核 CPU:
CPU Core 1 → L1/L2 缓存 ──┐
CPU Core 2 → L1/L2 缓存 ──┼→ 主内存
CPU Core 3 → L1/L2 缓存 ──┘

问题:多核处理器各自有缓存,导致缓存不一致。

1.2 并发问题根源

// 共享变量
int count = 0;

// 线程 A
count++; // 1. 读取主内存到工作内存
         // 2. count + 1
         // 3. 写回主内存

// 线程 B 同时执行 count++
// 结果可能丢失更新!

1.3 JMM 的作用

JMM 屏蔽底层硬件差异

定义线程如何与工作内存、主内存交互

保证原子性、可见性、有序性

二、JMM 核心概念

2.1 主内存与工作内存

graph TB
    subgraph 主内存
        M[共享变量]
    end
    
    subgraph 线程 A
        A1[A 的工作内存]
        A2[A 的变量副本]
    end
    
    subgraph 线程 B
        B1[B 的工作内存]
        B2[B 的变量副本]
    end
    
    M -->|读取 | A1
    A1 -->|写入 | M
    M -->|读取 | B1
    B1 -->|写入 | M

规则

2.2 内存间交互操作

操作说明
lock锁定主内存变量
unlock解锁主内存变量
read从主内存读取到工作内存
load将 read 的值放入工作内存副本
use将工作内存值传递给执行引擎
assign将执行引擎结果赋值给工作内存
store将工作内存值传送到主内存
write将 store 的值写入主内存变量

三、三大特性

3.1 原子性 (Atomicity)

定义:一个或多个操作要么全部执行成功,要么全部失败。

// ❌ 不保证原子性
int count = 0;
count++; // 非原子操作(读 - 改 - 写)

// ✅ 保证原子性
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作

JMM 保证

3.2 可见性 (Visibility)

定义:一个线程修改共享变量后,其他线程能立即看到。

// ❌ 不保证可见性
boolean flag = false;

// 线程 A
flag = true; // 修改后可能还在工作内存

// 线程 B
while (!flag) { // 可能永远看不到修改
    // ...
}

// ✅ 保证可见性
volatile boolean flag = false;
// volatile 强制刷新到主内存

JMM 保证

3.3 有序性 (Ordering)

定义:程序执行顺序按照代码的先后顺序。

// 可能发生指令重排
int a = 1;    // 1
int b = 2;    // 2
int c = a + b; // 3

// 优化后可能变成:2 → 1 → 3

JMM 保证


四、happens-before 规则

4.1 规则定义

happens-before:如果一个操作 happens-before 另一个操作,那么前者的结果对后者可见。

4.2 八大规则

1. 程序次序规则
   线程内每个操作 precedes 后续操作

2. 锁规则
   unlock 操作 happens-before 后续 lock 操作

3. volatile 规则
   volatile 写操作 happens-before 后续 volatile 读

4. 传递性规则
   A happens-before B, B happens-before C → A happens-before C

5. 线程启动规则
   Thread.start() happens-before 线程内任何操作

6. 线程终止规则
   线程内任何操作 happens-before Thread.join()

7. 线程中断规则
   interrupt() happens-before 被中断线程检测到

8. 对象终结规则
   对象构造完成 happens-before finalize()

4.3 实战示例

// 示例 1:volatile 保证有序性
class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // volatile 防止重排
                }
            }
        }
        return instance;
    }
}

// 示例 2:synchronized 保证可见性
class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++; // 释放锁前刷新到主内存
    }
    
    public synchronized int getCount() {
        return count; // 获取锁后从主内存读取
    }
}

五、内存屏障

5.1 什么是内存屏障

内存屏障 = CPU 指令 + 编译器屏障

作用:
1. 禁止屏障两侧的指令重排序
2. 强制刷新缓存到主内存

5.2 四种屏障类型

屏障类型作用
LoadLoad禁止读 - 读重排序
StoreStore禁止写 - 写重排序
LoadStore禁止读 - 写重排序
StoreLoad禁止写 - 读重排序(最严格)

5.3 volatile 实现原理

// volatile 写操作
volatile int a = 1;

// 插入屏障
storeStore(); // 禁止上面的写与下面的写重排
storeLoad();  // 禁止上面的写与下面的读重排

// volatile 读操作
int b = a;

// 插入屏障
loadLoad();   // 禁止下面的读与上面的读重排
loadStore();  // 禁止下面的读与上面的写重排

六、指令重排

6.1 重排原因

编译器优化 → 指令重排
CPU 乱序执行 → 指令重排
内存系统优化 → 指令重排

6.2 重排示例

// 经典重排案例
int x = 0;      // 1
boolean flag = false; // 2

// 线程 A
x = 1;          // 3
flag = true;    // 4

// 线程 B
while (flag) {  // 5
    System.out.println(x); // 6
}

// 可能重排为:4 → 3 → 5 → 6
// 线程 B 看到 flag=true,但 x 还是 0!

6.3 禁止重排

// 使用 volatile 禁止重排
volatile boolean flag = false;

// 线程 A
x = 1;
flag = true; // volatile 写,禁止与上面重排

// 线程 B
while (flag) { // volatile 读
    System.out.println(x); // 保证看到 x=1
}

七、final 字段语义

7.1 final 保证可见性

class FinalExample {
    final int x;
    int y;
    
    public FinalExample() {
        x = 3;
        y = 4;
    }
}

// 构造对象
FinalExample obj = new FinalExample();

// 其他线程读取
int a = obj.x; // ✅ 保证看到 3(final)
int b = obj.y; // ❌ 可能看到 0(非 final)

7.2 写 final 域的重排序规则

构造函数内:
1. 在 final 域写之后,不能将构造函数的引用赋值给外部变量
2. 保证 final 域初始化完成后才能被其他线程看到

八、总结

JMM 核心要点:

特性保证机制实现方式
原子性synchronized、Lock、Atomic锁、CAS
可见性volatile、synchronized内存屏障、锁
有序性volatile、happens-before内存屏障
规则说明
happens-before判断数据依赖关系
内存屏障禁止指令重排
volatile轻量级同步机制

JMM 是并发编程的基础,理解它有助于编写正确的多线程代码。


分享这篇文章到:

上一篇文章
Atomic 原子类详解
下一篇文章
CompletableFuture 异步编程实战