菜单

Administrator
发布于 2026-05-18 / 1 阅读
0
0

图解 Java 内存模型 (JMM)

图解 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 中最轻量级的同步机制,它提供两个保证:

  1. 可见性:对一个 volatile 变量的写,会立即刷新到主内存,其他线程读取时总能读到最新值
  2. 有序性:禁止对 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 内置的互斥锁,提供:

  1. 原子性:被保护的代码块不会被中断,同一时刻只有一个线程执行
  2. 可见性:锁释放前对共享变量的修改,对后续获取锁的线程可见
  3. 有序性:加锁-解锁之间的代码不会被重排序到临界区之外

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 并发的你有所帮助。


评论