Java 内存模型(JMM)详解
一、JMM 是什么一句话概括JMM Java Memory ModelJava 内存模型。JMMJava Memory Model是 Java 对多线程如何安全地读写共享变量制定的一套规则。也就是解决并发编程中的内存可见性、指令重排序和有序性等问题。你可以把它理解为Java 给程序员和 JVM 之间签订的合同。合同规定了线程什么时候能看到其他线程修改的变量代码的执行顺序能不能被重排哪些操作是原子性的为什么要签合同因为现代 CPU 为了性能会做各种优化缓存、指令重排导致多线程程序的行为可能和你写的代码顺序不一致。JMM 就是来规范这些行为的。二、核心概念主内存 vs 工作内存这是理解 JMM 的关键。JMM 把内存抽象成两层┌─────────────────────────────────────┐ │ 主内存Main Memory │ ← 所有共享变量真正存储的地方 │ 堆内存实例变量、静态变量等 │ └─────────────────────────────────────┘ ↑↓ read / write ┌──────┴──────┐ │ 线程 A │ ← 每个线程有自己的工作内存 │ 工作内存 │ CPU 缓存 寄存器的抽象 │ (副本) │ └─────────────┘ ↑↓ load / use / assign / store ┌──────┴──────┐ │ 线程 B │ │ 工作内存 │ │ (副本) │ └─────────────┘多核并发缓存架构JMM 中的工作内存在硬件层面主要对应CPU 缓存/寄存器等高速存储原因CPU 和 主内存之间有执行效率的差异。三级cpu高速缓存三、JMM 要解决的三大问题1. 可见性Visibility问题一个线程修改了共享变量其他线程能不能立刻看到public class VisibilityDemo { // 没有 volatile问题可能复现不了但逻辑上是错的 private static boolean flag false; public static void main(String[] args) throws InterruptedException { new Thread(() - { while (!flag) { // 空循环等待 flag 变成 true } System.out.println(子线程看到了 flagtrue退出循环); }).start(); Thread.sleep(1000); // 确保子线程先启动 flag true; // 主线程修改 flag System.out.println(主线程把 flag 改成了 true); } }可能的结果子线程永远看不到flag true陷入死循环。因为子线程的工作内存里缓存了flag false主内存的更新没有通知到它。解决用volatile关键字或者synchronized、Lock。2. 原子性Atomicity问题一个操作会不会执行到一半就被其他线程打断public class AtomicityDemo { private static int count 0; public static void main(String[] args) throws InterruptedException { for (int i 0; i 1000; i) { new Thread(() - { for (int j 0; j 1000; j) { count; // 这行代码不是原子操作 } }).start(); } Thread.sleep(3000); System.out.println(count count); // 大概率小于 1,000,000 } }为什么count不是原子的因为它实际上是三步读取count的值从工作内存加 1写回count如果线程 A 读到 5还没写回线程 B 也读到 5各自加 1 后都写回 6就少了一次。解决用synchronized、AtomicInteger、Lock。3. 有序性Ordering问题代码的执行顺序和你写的顺序可能不一样编译器、JIT、CPU为了优化性能会重排指令Instruction Reordering只要单线程结果不变就行。但多线程下可能出问题public class OrderingDemo { int a 0; boolean ready false; // 线程 A 执行 void writer() { a 1; // 步骤 1 ready true; // 步骤 2 } // 线程 B 执行 void reader() { if (ready) { // 步骤 3 System.out.println(a); // 步骤 4可能输出 0 } } }重排后可能的执行顺序步骤 2 → 步骤 3 → 步骤 1 → 步骤 4线程 B 看到ready true但a还没被赋值为 1所以输出 0。解决用volatile禁止指令重排或者synchronized。JMM 会规定哪些重排序允许发生哪些情况下必须保证一定的执行顺序。小结JMM │ ├── 可见性 │ └── volatile │ ├── 有序性 │ └── volatile / synchronized │ ├── 原子性 │ └── synchronized / CAS / AtomicXXX │ └── happens-before └── 规定“一个操作的结果什么时候对另一个操作可见”JMM是 Java 定义的一套多线程内存访问规范主要规定线程如何与共享变量进行交互从而解决可见性、有序性和原子性等并发问题。可以把 JMM 暂时牢牢记成两个字规则。四、JMM 的黄金法则Happens-BeforeJMM 定义了一系列规则告诉你在什么情况下一个操作的结果对另一个操作是可见的。这就是Happens-Before规则。更准确地说如果操作 A happens-before 操作 B那么 Java 内存模型保证A 操作的结果对 B 操作是可见的。A happens-before B 意味着 A 的结果 ↓ 对 B 一定可见【注意】happens-before 不等于“时间上的先后”而是前面的操作结果对后面的操作一定可见。1、happens-before 最常见的规则你不需要记住全部但要知道常用的几条规则含义程序次序规则同一个线程中前面的操作 Happens-Before 后面的操作volatile 规则对volatile变量的写Happens-Before 后续对该变量的读锁规则解锁unlockHappens-Before 后续的加锁lock线程启动规则Thread.start()Happens-Before 线程内的所有操作线程终止规则线程内的所有操作 Happens-Before 其他线程检测到线程终止传递性如果 A Happens-Before BB Happens-Before C那么 A Happens-Before C用volatile举个例子volatile int a 0; // 线程 A a 1; // 写 volatile 变量 // 线程 B int b a; // 读 volatile 变量根据volatile 规则线程 A 的写操作 Happens-Before 线程 B 的读操作。所以线程 B 一定能看到a 1。volatile是 JMM 定义的一种 happens-before 规则的载体/手段。JMM 是“规则制定者”happens-before 是其中非常核心的一套规则而volatile、synchronized、start()、join()等机制可以建立特定的 happens-before 关系从而保证线程之间的数据可见性。线程启动规则int x 10; Thread t new Thread(() - { System.out.println(x); }); t.start();线程启动之前主线程 x 10 ↓ t.start() ↓ 新线程 读取 xJMM 规定线程 A 执行Thread.start()之前的操作happens-before新线程中的操作。所以新线程可以看到x 10也就是说start()之前的操作 ↓ happens-before ↓ 新线程中的操作五、volatile 关键字JMM 的轻量级解决方案volatile是 JMM 中最重要的关键字之一它做了两件事保证可见性一个线程修改了volatile变量其他线程立即可见强制刷新主内存。禁止指令重排编译器不会把volatile变量前后的指令乱序。但注意volatile不保证原子性volatile int count 0; count; // 仍然不是线程安全的什么时候用volatile状态标志位如boolean running true单例模式的double-checked locking一个线程写、多个线程读的场景六、synchronized 与 JMMsynchronized是 JMM 的重量级解决方案它同时保证了原子性同一时间只有一个线程能执行同步代码块。可见性线程释放锁时会把工作内存的修改刷新到主内存获取锁时会从主内存重新读取。有序性同步代码块内部不会重排到外部。synchronized (this) { count; // 现在这是线程安全的了 }七、一张图总结 JMM┌─────────────────────────────────────────┐ │ Java 内存模型 (JMM) │ ├─────────────────────────────────────────┤ │ 抽象结构主内存 ←→ 各线程的工作内存 │ ├─────────────────────────────────────────┤ │ 三大特性 │ │ • 可见性 — volatile、synchronized │ │ • 原子性 — synchronized、Atomic 类 │ │ • 有序性 — volatile、synchronized │ ├─────────────────────────────────────────┤ │ 核心规则Happens-Before │ │ 定义了什么时候能看到其他线程的修改 │ └─────────────────────────────────────────┘

相关新闻