Java学习第七十六部分——Java内存模型 (JMM)

目录

一、前言概述提要

二、关键作用解释

三、核心概念剖析

四、问题解决方案

五、happens-before原则

六、总结归纳升华


一、前言概述提要

       Java内存模型 (Java Memory Model, JMM)是Java 虚拟机 (JVM)规范中定义的一个核心概念,它规定了多线程环境下,线程如何与主内存交互以及线程之间如何通过主内存共享变量。它的主要目的是解决多线程编程中常见的三大核心问题:可见性、有序性、原子性

二、关键作用解释

作用 核心要点
屏蔽底层差异 ✅ 统一抽象:隐藏CPU架构(x86/ARM)和操作系统的内存模型差异
✅ 一次编写,到处运行:确保并发代码在所有JVM实现上行为一致
定义并发语义 ✅ 可见性:线程修改共享变量后,其他线程何时能看到
✅ 有序性:明确指令重排序规则
✅ Happens-Before:跨线程操作顺序的强制保证
提供并发基础 ✅ 支撑关键字synchronized(原子性+可见性)、volatile(禁用重排序+强刷新)
✅ 实现并发工具java.util.concurrent包的底层保障机制

总结:

  • 统一平台差异 → 写一套代码,跑在任何JVM上

  • 立规矩 → 线程间何时可见、指令如何排序

  • 给武器 → 让synchronized/volatile等关键字真正生效

三、核心概念剖析

1.  主内存: 存储所有共享变量(实例字段、静态字段、构成数组对象的元素)的区域。所有线程都可以访问主内存。
2.  工作内存: 每个线程都有一个私有的工作内存而工作内存又存储了该线程使用到的共享变量的副本。

  • 线程对变量的所有操作(例如读取、赋值)都必须在工作内存中进行,不能直接读写主内存中的变量。

  • 工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了 CPU 寄存器、各级缓存(L1/L2/L3 Cache)、写缓冲区、硬件优化等。

3.  内存间交互操作: JMM 定义了 8 种原子操作来描述线程如何将数据在主内存和工作内存之间拷贝和同步。

  • lock (锁定) - 作用于主内存变量,标识为线程独占。

  • unlock(解锁) - 作用于主内存变量,释放锁定状态。

  • read(读取) - 作用于主内存变量,把值传输到线程工作内存。

  • load(载入) - 作用于工作内存变量,把 `read` 操作得到的值放入工作内存的变量副本中。

  • use(使用) - 作用于工作内存变量,把值传递给执行引擎(如 CPU)。

  • assign(赋值) - 作用于工作内存变量,把从执行引擎接收到的值赋给工作内存变量。

  • store(存储) - 作用于工作内存变量,把值传输到主内存。

  • write(写入) - 作用于主内存变量,把 `store` 操作传输过来的值放入主内存变量中。

ps:`read/load` 和 `store/write` 必须成对、顺序执行(但不保证连续执行),`assign` 和 `store` 之间可以有其他操作。`lock`/`unlock` 操作隐式地包含在 `monitorenter`/`monitorexit` 字节码(即 `synchronized` 块)中。

四、问题解决方案

特性 问题根源 JMM解决方案
可见性 变量副本存在于各线程的工作内存中,线程修改私有副本后未及时同步到主内存,导致其他线程读取旧值。 volatile
- 写操作立即刷新到主内存
- 读操作强制从主内存重新加载
- 内存屏障:写(store-store)、读(load-load)
synchronized
- 进入同步块(monitorenter):清空工作内存 → 重载主内存值(acquire语义)
- 退出同步块(monitorexit):强制刷新修改到主内存(release语义)
final:构造函数中正确初始化的final字段对其他线程可见
有序性 1. 编译器重排序:优化指令顺序(不改变单线程语义)
2. 处理器重排序:CPU乱序执行
3. 内存系统重排序:缓存导致写入顺序不一致
volatile
- 内存屏障禁止重排序(store-store, store-load, load-load, load-store)
synchronized
- monitorenter/monitorexit建立内存屏障(acquire/release语义),禁止临界区内操作重排序到临界区外
happens-before原则:JMM核心规则,定义操作间的全局有序性保证
原子性 简单操作(如i++)在底层由多步骤组成(读→改→写),线程切换可能导致步骤中断 基本数据类型:除long/double的非volatile变量外,读写本身原子
synchronized
- 互斥访问保证同步块内操作原子执行
java.util.concurrent.atomic
- 基于CAS的原子类(如AtomicInteger),提供原子方法

内存屏障

  • volatile写屏障:阻止写操作之前的任意操作重排序到写之后

  • volatile读屏障:阻止读操作之后的任意操作重排序到读之前

  • synchronized屏障:临界区代码不会重排序到锁获取前或锁释放后

long/double的非原子性例外

  • JVM允许64位long/double的非volatile变量读写被拆分为两个32位操作,需用volatile声明保证原子性。

happens-before原则

  • 涵盖锁规则、volatile规则、线程启动规则等,是解决有序性问题的核心基础。

五、happens-before原则

  • 它是 JMM 的核心规则,用于描述两个操作之间的可见性和顺序关系。

  • 如果操作 A *happens-before* 操作 B,那么

  1. A 操作的结果(对共享变量的修改)对 B 操作是可见的。

  2. A 操作的执行顺序排在B操作之前(从程序逻辑的角度看)。

  • JMM 定义了一系列天然的 *happens-before* 规则:

  1. 程序顺序规则: 在同一个线程中,按照程序代码顺序,前面的操作 *happens-before* 于后续的任意操作(`as-if-serial` 语义)。

  2. 监视器锁规则: 对一个锁的解锁操作 *happens-before* 于后续对这个锁的加锁操作。

  3. `volatile` 变量规则: 对一个 `volatile` 变量的写操作 *happens-before* 于后续对这个 `volatile` 变量的读操作。

  4. 线程启动规则: `Thread.start()` 调用 *happens-before* 于新线程中的任何操作。

  5. 线程终止规则: 线程中的所有操作都 *happens-before* 于其他线程检测到该线程已经终止(如 `Thread.join()` 成功返回或 `Thread.isAlive()` 返回 `false`)。

  6. 中断规则: 线程 A 调用 `ThreadB.interrupt()` *happens-before* 于线程 B 检测到中断(如抛出 `InterruptedException` 或调用 `Thread.interrupted()`/`Thread.isInterrupted()` 返回 `true`)。

  7. 对象终结规则: 对象的构造函数结束(`finalize()` 方法执行前) *happens-before* 于该对象的 `finalize()` 方法的开始。

  8. 传递性: 如果 A *happens-before* B,且 B *happens-before* C,那么 A *happens-before* C。

六、总结归纳升华

       Java 内存模型 (JMM) 是 Java 并发编程的基石。它通过定义主内存工作内存它们之间的交互协议,并借助 `volatile`、`synchronized`、`final` 关键字以及强大的 `happens-before` 原则,为开发者提供了在多线程环境下控制可见性、有序性、原子性的机制。

你可能感兴趣的:(Java学习第七十六部分——Java内存模型 (JMM))