图解 Java 内存模型(JMM)
一、引言
Java 内存模型(Java Memory Model,JMM)是 Java 并发编程的核心规范,它定义了多线程环境下共享变量的访问规则。理解 JMM 是写出正确、高效并发程序的必经之路。
💡 一句话总结:JMM 规定了一个线程如何看到另一个线程对共享变量的修改,以及如何同步地对共享变量进行读写。
二、JMM 规范概览
JMM 是 Java 虚拟机规范的一部分,它屏蔽了不同硬件平台和操作系统之间的内存访问差异,为 Java 程序员提供了一致的、可预测的内存可见性保证。
┌──────────────────────────────────────────────────────────────┐
│ Java 内存模型 (JMM) │
├──────────────────────────────────────────────────────────────┤
│ ┌───────────────────┐ ┌───────────────────────┐ │
│ │ 主内存 (Main │ <─────> │ 工作内存 (Working │ │
│ │ Memory) │ │ Memory) │ │
│ │ (所有线程共享) │ │ (每个线程私有) │ │
│ └───────────────────┘ └───────────────────────┘ │
│ │
│ 核心保证: │
│ • 原子性 (Atomicity) —— 操作不可中断 │
│ • 可见性 (Visibility) —— 一个线程修改,其他线程立即可见 │
│ • 有序性 (Ordering) —— 禁止指令重排导致的意外行为 │
│ • Happens-Before 规则 —— 判断数据竞争的依据 │
└──────────────────────────────────────────────────────────────┘
2.1 JSR-133 规范
JMM 的核心规范定义在 JSR-133(Java Memory Model and Thread Specification)中,自 JDK 5 起正式生效。它解决了早期 JMM 中的诸多缺陷,例如 volatile 语义模糊、final 字段初始化安全性不足等问题。
三、主内存 vs 工作内存
JMM 规定所有共享变量存储在主内存中,每个线程拥有自己的工作内存(可以类比为 CPU 缓存)。
3.1 内存模型架构图
┌─────────────┐
│ 主内存 │
│ (堆) │
│ ┌─────┐ │
│ │变量 X│ │
│ │= 42 │ │
│ └─────┘ │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────────▼────────┐ ┌─────▼────────┐ ┌─────▼────────┐
│ 线程 A │ │ 线程 B │ │ 线程 C │
│ ┌────────────┐ │ │ ┌───────────┐│ │ ┌───────────┐│
│ │ 工作内存 │ │ │ │ 工作内存 ││ │ │ 工作内存 ││
│ │ ┌──┐ │ │ │ │ ┌──┐ ││ │ │ ┌──┐ ││
│ │ │X │= 42 │ │ │ │ │X │= 0 ││ │ │ │X │= 99 ││
│ │ └──┘ │ │ │ │ └──┘ ││ │ │ └──┘ ││
│ └────────────┘ │ │ └───────────┘│ │ └───────────┘│
└─────────────────┘ └──────────────┘ └──────────────┘
3.2 交互协议
线程对共享变量的操作必须遵循以下协议:
┌─────────────────────────────────────────────────────┐
│ 内存交互操作 (8 种) │
├─────────────────────────────────────────────────────┤
│ 主内存 ◄────────────── 工作内存 │
│ │
│ lock (锁定) │ read (读取) │ load (载入) │
│ unlock (解锁) │ store (存储) │ write (写入) │
│ use (使用) │ assign (赋值) │ │
│ │
│ 读取流程: read → load → use │
│ 写入流程: assign → store → write │
└─────────────────────────────────────────────────────┘
3.3 代码示例
public class JMMDemo {
private static int count = 0; // 共享变量
public static void main(String[] args) {
// 线程 A 修改 count
new Thread(() -> {
count = 10; // 1. assign 到工作内存
// 2. store 到主内存
// 3. write 到主内存变量
}).start();
// 线程 B 读取 count
new Thread(() -> {
// 1. read 从主内存
// 2. load 到工作内存
// 3. use 在本地使用
System.out.println(count); // 可能输出 0,也可能输出 10
}).start();
}
}
四、三大特性:原子性、可见性、有序性
4.1 原子性 (Atomicity)
定义:一个或多个操作要么全部执行且不被打断,要么全部不执行。
原子操作示例:
┌─────┐ ┌─────┐ ┌─────┐
│ 读 │ ──→ │ 改 │ ──→ │ 写 │ ← 原子操作,不可分割
└─────┘ └─────┘ └─────┘
非原子操作示例 (count++):
┌────────┐ ┌──────────┐ ┌────────┐
│ 读取 │ │ 自增运算 │ │ 写入 │
│ count │ ─→ │ count+1 │ ─→ │ count │ ← 可能被线程切换打断
└────────┘ └──────────┘ └────────┘
JMM 保证的原子操作:
- 对基本类型变量(除
long/double外)的读写是原子的 - 所有
volatile变量的读写是原子的(包括long/double) synchronized块内的操作是原子的
public class AtomicityExample {
private static int counter = 0;
// ❌ 非原子操作:counter++ 不是原子的
public static void increment() {
counter++; // 读-改-写三步
}
// ✅ 原子操作:使用 synchronized
public synchronized static void safeIncrement() {
counter++;
}
// ✅ 原子操作:使用 AtomicInteger
private static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void atomicIncrement() {
atomicCounter.incrementAndGet();
}
}
4.2 可见性 (Visibility)
定义:当一个线程修改了共享变量,其他线程能立即看到这个修改。
不可见问题:
线程 A 线程 B
┌──────┐ ┌──────┐
│ X=1 │ │ X=0 │ ← 看不到 A 的修改!
│ (写) │ │ (读) │
└──────┘ └──────┘
│ │
└──── 主内存: X=1 ──────┘
(B 没有刷新工作内存)
可见性解决方案:
volatile / synchronized / final
public class VisibilityExample {
private static boolean flag = false;
private static int number = 0;
// ❌ 没有可见性保证
public static class Stopper {
boolean stopped = false;
void stop() { stopped = true; } // 写线程
boolean isStopped() { return stopped; } // 读线程(可能永远看不到 true)
}
// ✅ volatile 保证可见性
public static class VolatileStopper {
volatile boolean stopped = false;
void stop() { stopped = true; } // volatile 写
boolean isStopped() { return stopped; } // volatile 读(立即看到最新值)
}
public static void main(String[] args) throws InterruptedException {
VolatileStopper stopper = new VolatileStopper();
new Thread(() -> {
while (!stopper.isStopped()) {
// 忙等待 —— 如果没有 volatile,这里可能死循环!
}
System.out.println("线程退出!");
}).start();
Thread.sleep(1000);
stopper.stop(); // 主线程修改标志位
}
}
4.3 有序性 (Ordering)
定义:程序执行的顺序按照代码的先后顺序执行。
编译器和 CPU 可能会对指令进行重排序(优化),但会遵守 as-if-serial 语义。
源代码顺序:
a = 1; // 写 a
b = 2; // 写 b
c = a + b; // 读 a, b
可能的重排序(合法):
b = 2; // 写 b 提前
a = 1; // 写 a 推后
c = a + b; // 结果不变
危险的重排序(多线程):
线程 A: 线程 B:
x = 1; y = 2;
flag = true; if (flag) {
int r = x; // 可能看到 x = 0!
}
public class ReorderingExample {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100000; i++) {
x = 0; y = 0; a = 0; b = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b; // 如果这里看到 x = 0,说明 y 先于 a 写入
});
Thread t2 = new Thread(() -> {
b = 1;
y = a; // 如果这里看到 y = 0,说明 x 先于 b 写入
});
t1.start(); t2.start();
t1.join(); t2.join();
// 理论上可能的结果: (x, y) = (0, 0) → 发生了重排序!
if (x == 0 && y == 0) {
System.out.println("第 " + i + " 次: 检测到重排序!");
}
}
}
}
五、Happens-Before 规则
Happens-Before 是 JMM 中判断数据竞争、保证内存可见性的核心规则集。如果操作 A Happens-Before 操作 B,那么 A 的结果对 B 可见。
5.1 规则总览
┌─────────────────────────────────────────────────────────────┐
│ Happens-Before 规则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ (1) 程序顺序规则 │
│ 线程中每个操作 happens-before 该线程中后续的任意操作 │
│ │
│ (2) volatile 规则 │
│ 对 volatile 变量的写 happens-before 后续对该变量的读 │
│ │
│ (3) 锁规则 │
│ 锁的解锁 happens-before 后续对该锁的加锁 │
│ │
│ (4) 线程启动规则 │
│ Thread.start() happens-before 该线程中的任意操作 │
│ │
│ (5) 线程终止规则 │
│ 线程中任意操作 happens-before 其他线程检测到该线程终止 │
│ │
│ (6) 线程中断规则 │
│ interrupt() 调用 happens-before 检测到中断事件 │
│ │
│ (7) finalizer 规则 │
│ 对象构造完成 happens-before finalize() 开始执行 │
│ │
│ (8) 传递性 │
│ 如果 A happens-before B,B happens-before C │
│ 则 A happens-before C │
│ │
└─────────────────────────────────────────────────────────────┘
5.2 规则可视化
volatile 规则:
┌──────────┐ happens-before ┌──────────┐
│ volatile │ ──────────────────→ │ volatile │
│ 写 (A) │ │ 读 (B) │
│ x = 1 │ │ r = x │ → r == 1
└──────────┘ └──────────┘
锁规则:
┌──────────┐ happens-before ┌──────────┐
│ 解锁 (A) │ ──────────────────→ │ 加锁 (B) │
│ 写入 y │ │ 读取 y │ → y 可见
└──────────┘ └──────────┘
传递性:
┌─────┐ ┌─────┐ ┌─────┐
│ lock │ ──→ │ 操作 │ ──→ │ unlock│
└─────┘ └─────┘ └─────┘
│ │
└────── happens-before ────┘
5.3 代码示例
public class HappensBeforeDemo {
private int a = 0;
private volatile boolean flag = false;
public void writer() {
a = 42; // 1. 普通写
flag = true; // 2. volatile 写
}
public void reader() {
if (flag) { // 3. volatile 读(看到 flag == true)
int i = a; // 4. 由于 happens-before 传递性,
// a = 42 对这里可见
System.out.println(i); // 一定输出 42
}
}
public static void main(String[] args) throws InterruptedException {
HappensBeforeDemo demo = new HappensBeforeDemo();
Thread t1 = new Thread(demo::writer);
Thread t2 = new Thread(demo::reader);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
六、内存屏障 (Memory Barrier)
内存屏障是一种 CPU 指令,用于禁止特定类型的指令重排序。JMM 通过在不同场景插入内存屏障来实现可见性和有序性。
6.1 屏障类型
┌──────────────────────────────────────────────────────────────┐
│ 内存屏障类型表 │
├────────────┬─────────────────────────────────────────────────┤
│ 屏障类型 │ 作用 │
├────────────┼─────────────────────────────────────────────────┤
│ LoadLoad │ 屏障前的读操作完成后,屏障后的读操作才能开始 │
│ StoreStore │ 屏障前的写操作完成后,屏障后的写操作才能开始 │
│ LoadStore │ 屏障前的读操作完成后,屏障后的写操作才能开始 │
│ StoreLoad │ 屏障前的写操作完成后,屏障后的读操作才能开始 │
│ │ (最耗性能,x86 上只使用这个) │
└────────────┴─────────────────────────────────────────────────┘
6.2 volatile 与内存屏障
volatile 写操作的内存屏障插入策略:
┌───────────────────────────────────────┐
│ 普通读/写操作 │
│ ┌───────────────────────────────────┐ │
│ │ StoreStore 屏障 │ │ ← 禁止 volatile 写与之前写重排序
│ └───────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────┐ │
│ │ volatile 写 (x = 1) │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ StoreLoad 屏障 │ │ ← 禁止 volatile 写与之后读重排序
│ └───────────────────────────────────┘ │
│ 后续读操作 │
└───────────────────────────────────────┘
volatile 读操作的内存屏障插入策略:
┌───────────────────────────────────────┐
│ 前续写操作 │
│ ┌───────────────────────────────────┐ │
│ │ LoadLoad 屏障 │ │ ← 禁止 volatile 读与之前读重排序
│ └───────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────┐ │
│ │ volatile 读 (r = x) │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ LoadStore 屏障 │ │ ← 禁止 volatile 读与之后写重排序
│ └───────────────────────────────────┘ │
│ 后续写/读操作 │
└───────────────────────────────────────┘
6.3 x86-TSO 下的优化
在 x86 架构下,由于 TSO(Total Store Order)模型,只有 StoreLoad 屏障是实际需要的:
// volatile 写的 x86 实现(实际上只插入 StoreLoad 屏障)
// 汇编指令: lock addl $0, 0(%rsp)
// 或: mfence
public class VolatileBarrierExample {
volatile int x;
public void write(int v) {
// 在 x86 上,volatile 写会触发缓存一致性协议
x = v; // 硬件层面相当于插入 StoreLoad 屏障
}
public int read() {
// volatile 读在 x86 上不需要特殊指令
return x; // 利用 MESI 协议保证可见性
}
}
七、volatile 语义
7.1 核心语义
volatile 是 Java 中最轻量级的同步机制,它提供两个保证:
- 可见性:对一个
volatile变量的写,会立即刷新到主内存,其他线程读取时总能读到最新值 - 有序性:禁止对
volatile变量的读写进行指令重排序
⚠️ 注意:
volatile不保证原子性!复合操作(如count++)仍需加锁。
7.2 读写语义图解
volatile 写-读建立 happens-before 关系:
线程 A (写) 线程 B (读)
┌────────────────┐ ┌────────────────┐
│ 普通变量写入 │ │ │
│ (a = 42) │ │ │
│ │ │ │
│ volatile 写入 │ ──────→ │ volatile 读取 │
│ (flag = true) │ 可见性 │ (r1 = flag) │
│ │ │ │
│ │ │ 普通变量读取 │
│ │ │ (r2 = a) → 42 │
└────────────────┘ └────────────────┘
7.3 正确使用模式
public class VolatilePatterns {
// ✅ 模式1: 状态标志位
private volatile boolean running = true;
public void stop() { running = false; }
public void run() {
while (running) {
// 执行任务
}
}
// ✅ 模式2: 一次性安全发布
private volatile SomeObject instance;
public void init() {
instance = new SomeObject(); // volatile 写确保完整构造
}
public SomeObject get() {
return instance; // volatile 读确保看到正确值
}
// ✅ 模式3: 独立观察(多个 volatile 变量彼此独立)
private volatile double temperature;
private volatile double humidity;
public void update(double t, double h) {
temperature = t; // volatile 写 1
humidity = h; // volatile 写 2
}
public double[] read() {
return new double[]{temperature, humidity};
}
// ❌ 错误模式: 依赖 volatile 保证复合操作的原子性
private volatile int counter = 0;
public void badIncrement() {
counter++; // 非原子!读-改-写三步
}
// ✅ 正确: 使用 AtomicInteger 或 synchronized
private AtomicInteger atomicCounter = new AtomicInteger(0);
public void goodIncrement() {
atomicCounter.incrementAndGet();
}
}
八、synchronized 语义
8.1 核心语义
synchronized 是 Java 内置的互斥锁,提供:
- 原子性:被保护的代码块不会被中断,同一时刻只有一个线程执行
- 可见性:锁释放前对共享变量的修改,对后续获取锁的线程可见
- 有序性:加锁-解锁之间的代码不会被重排序到临界区之外
8.2 锁的获取与释放
synchronized 的 happens-before 关系:
线程 A(加锁 → 操作 → 解锁) 线程 B(加锁 → 读操作)
┌─────────────────────┐
│ 尝试获取锁 (可能阻塞) │
└─────────┬───────────┘
│ 成功获取锁
▼
┌─────────────────────┐
│ 临界区操作 │
│ a = 1 │
│ b = 2 │
└─────────┬───────────┘
│ 释放锁
▼
┌─────────────────────┐ happens-before
│ 释放锁 (unlock) │ ──────────────────────────→
└─────────────────────┘ │
│
┌────────────▼──────────┐
│ 获取锁 (lock) │
└────────────┬──────────┘
│
▼
┌─────────────────────┐
│ 读取 a → 1, b → 2 │
│ (A 的修改全部可见) │
└─────────────────────┘
8.3 字节码层面的实现
public class SynchronizedExample {
private final Object lock = new Object();
private int count = 0;
// ✅ 同步代码块
public void increment() {
synchronized (lock) { // monitorenter
count++; // 临界区
} // monitorexit
}
// ✅ 同步方法(隐式 this 锁)
public synchronized void syncMethod() {
count++;
}
// ✅ 静态同步方法(Class 对象锁)
public static synchronized void staticSyncMethod() {
// ...
}
}
对应的字节码(简化):
// 同步代码块的字节码
aload_0
getfield #lock
dup
astore_1
monitorenter ← 进入,获取锁
aload_0
dup
getfield #count
iconst_1
iadd
putfield #count
aload_1
monitorexit ← 正常退出,释放锁
// ... 异常处理也会释放锁
// 同步方法的字节码
flags: ACC_SYNCHRONIZED ← 方法级标记,不是 monitorenter/exit
// 调用时隐式获取锁,返回时隐式释放
8.4 性能演变(锁优化)
JDK 6 之后的锁优化:
无锁 ──→ 偏向锁 ──→ 轻量级锁 ──→ 重量级锁
(CAS) (单线程) (自旋) (OS 互斥量)
锁状态升级(不可逆):
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ 无锁 │ ──→ │ 偏向锁 │ ──→ │ 轻量级锁│ ──→ │ 重量级锁│
│ │ │ │ │ │ │ │
│ 初始态 │ │ 单线程 │ │ 少量竞争│ │ 大量竞争│
└────────┘ └────────┘ └────────┘ └────────┘
特点:
偏向锁: 避免 CAS 开销,有竞争就撤销
轻量级锁: 通过 CAS + 自旋,避免线程阻塞
重量级锁: 线程阻塞,上下文切换开销大
九、final 语义
9.1 final 的可见性保证
final 字段在 JMM 中有特殊的初始化安全性保证:当对象构造完成后,其他线程看到该对象时,一定能看到正确初始化的 final 字段值。
final 域的写屏障:
┌─────────────────────────────────────┐
│ 构造方法中 │
│ │
│ final int x = 42; ← 写入 final │
│ int y = 10; ← 普通字段 │
│ │
│ ┌───────────────────────────────┐ │
│ │ StoreStore 屏障 (JSR-133) │ │ ← 禁止 final 写重排序到构造方法外
│ └───────────────────────────────┘ │
│ │
│ this 引用被发布 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 读取线程 │
│ │
│ obj = getObject(); │
│ obj.x → 一定看到 42 │ ← final 保证
│ obj.y → 可能看到 0(未初始化) │ ← 普通字段无保证
└─────────────────────────────────────┘
9.2 代码示例
public class FinalExample {
private final int x; // final 字段
private int y; // 普通字段
private static FinalExample instance;
public FinalExample() {
x = 42; // final 写
y = 10; // 普通写
}
public static void writer() {
instance = new FinalExample(); // 构造方法中写入 final x = 42
}
public static void reader() {
FinalExample obj = instance;
if (obj != null) {
int a = obj.x; // 一定看到 42 ✅
int b = obj.y; // 可能看到 0 ❌(未初始化)
System.out.println("x = " + a + ", y = " + b);
}
}
// ⚠️ 禁止"溢出构造方法"发布 this 引用
public FinalExample(int value) {
this.x = value;
// ❌ 危险!构造方法还没执行完就把 this 发布出去了
GlobalHolder.instance = this;
}
}
9.3 final 与不可变对象
// ✅ 利用 final 构建线程安全的不可变对象
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// 返回新对象而非修改
public ImmutablePoint translate(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
}
// 不可变对象自动具有线程安全性:
// 1. final 保证构造安全性
// 2. 所有字段不可修改
// 3. 无 setter 方法
十、综合实例:从 JMM 角度看双重检查锁定
双重检查锁定(Double-Checked Locking, DCL)是单例模式的经典实现,它的正确性完全依赖于 JMM 的保证。
public class Singleton {
// ❌ 错误实现(无 volatile)
private static Singleton instance;
// ✅ 正确实现(JDK 5+)
private static volatile Singleton instance; // volatile 是关键!
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查(加锁)
instance = new Singleton(); // 问题在这里!
}
}
}
return instance;
}
}
10.1 无 volatile 时的重排序问题
new Singleton() 的分解步骤:
1. memory = allocate() // 分配内存
2. ctorSingleton(memory) // 执行构造方法
3. instance = memory // 设置引用
可能的重排序(步骤 2 和 3 互换):
1. memory = allocate() // 分配内存
2. instance = memory // 设置引用(对象未初始化!)
3. ctorSingleton(memory) // 执行构造方法
线程 B 在步骤 2 后看到 instance != null:
┌────────────┐ ┌────────────┐
│ 线程 A │ │ 线程 B │
│ ① 分配内存 │ │ │
│ ② 设置引用 │ ──────→ │ ③ instance │
│ (instance)│ 可见 │ != null │
│ ④ 构造方法 │ │ ⑤ 直接返回 │
│ (未执行) │ │ 未初始化 │
└────────────┘ └────────────┘
10.2 volatile 如何解决
// volatile 禁止了重排序
volatile Singleton instance;
// 相当于在 new 操作前后插入了内存屏障:
//
// StoreStore 屏障 ← 禁止 2→3 重排序
// instance = new Singleton();
// StoreLoad 屏障 ← 保证对其他线程可见
10.3 推荐替代方案
// ✅ 方案1: 静态内部类(推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 类加载机制天然线程安全
}
}
// ✅ 方案2: 枚举(最简洁,防反射/序列化攻击)
public enum Singleton {
INSTANCE;
public void doSomething() {
// ...
}
}
十一、总结
11.1 JMM 核心要点回顾
┌─────────────────────────────────────────────────────────────┐
│ JMM 速查表 │
├───────────────┬───────────────────────┬─────────────────────┤
│ 机制 │ 可见性 │ 有序性 │
├───────────────┼───────────────────────┼─────────────────────┤
│ volatile │ 立即刷新到主内存 │ 禁止重排序 │
│ synchronized│ 解锁时刷新到主内存 │ 临界区内禁止重排序 │
│ final │ 构造完成后立即可见 │ 特殊 StoreStore 屏障│
│ CAS │ 底层 cmpxchg 保证 │ CPU 指令级保证 │
└───────────────┴───────────────────────┴─────────────────────┘
11.2 选择建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单状态标志 | volatile |
最轻量,足够使用 |
| 计数器 | AtomicInteger / LongAdder |
无锁 CAS,高性能 |
| 复合操作 | synchronized / ReentrantLock |
保证原子性 |
| 不可变对象 | final + 无 setter |
天然线程安全 |
| 读多写少 | ReadWriteLock / StampedLock |
读写分离,提升性能 |
| 生产者-消费者 | BlockingQueue |
封装好的线程安全队列 |
11.3 写在最后
理解 JMM 不是为了记住那些底层细节,而是为了在编写并发程序时能够正确推理:
- 我是否对共享变量做了同步?
- 是否建立了足够的 happens-before 关系?
- 是否存在指令重排序导致的隐患?
- 我的代码在弱内存模型(如 ARM)上还能正确运行吗?
JMM 的本质:在程序性能和内存一致性之间取得平衡,让程序员能够写出既高效又正确的并发代码。
本文通过 ASCII 图表和代码示例,尝试以直观的方式呈现 Java 内存模型的核心概念。希望对正在学习 Java 并发的你有所帮助。