什么是 Java 内存模型(JMM)?

Java 内存模型(JMM)定义了线程如何通过内存进行交互,规定所有变量存储在主内存,线程操作需将变量加载到工作内存,修改后写回主内存。核心是解决多线程并发时的原子性、可见性和有序性问题,通过 ​​volatile​​​、​​synchronized​​​ 等关键字和 ​​final​​ 保证线程安全。

一、JMM 的核心目标

JMM 的核心目标是 定义程序中变量的访问规则,解决多线程并发时的三大核心问题:

  1. 原子性
    指一个操作是不可分割的整体,要么全部执行,要么全部不执行。
  • 例:​i++​ 并非原子操作(实际包含读取、加 1、写回三步),多线程下可能出现数据丢失。
  • 保证方式:​synchronized​​Lock​​java.util.concurrent.atomic​ 原子类。
  1. 可见性
    指一个线程修改的变量值,能被其他线程立即感知到。
  • 原因:线程操作变量时,会先将主内存的变量加载到自己的 工作内存(CPU 缓存),修改后仅更新工作内存,未及时写回主内存,导致其他线程读取到旧值。
  • 保证方式:​volatile​(强制写回主内存并 invalidate 其他线程的缓存)、​synchronized​​final​
  1. 有序性
    指程序执行的顺序与代码编写的顺序一致。
  • 原因:CPU 为优化性能会进行 指令重排序(单线程下不影响结果,但多线程下可能破坏依赖关系)。
  • 例:​int a = 1; int b = 2;​ 可能被重排为 ​int b = 2; int a = 1;​,若另一个线程依赖 ​a​ 先赋值,则可能出错。
  • 保证方式:​volatile​(禁止重排序)、​synchronized​​final​,以及 ​happens-before​ 规则。

二、JMM 的内存结构模型

JMM 抽象了线程与内存的交互关系,将内存分为两类:

内存类型 作用 访问方式
主内存 存储所有线程共享的变量(实例变量、静态变量) 线程需通过工作内存间接访问
工作内存 每个线程独有的私有内存(CPU 缓存 + 寄存器) 线程直接读写,速度快
线程操作变量的流程:
  1. 线程从主内存读取变量到工作内存(加载 load);
  2. 线程在工作内存中修改变量(使用 use);
  3. 修改后的变量写回主内存(存储 store)。

⚠️ 问题:多线程并发时,若多个线程同时修改同一变量,可能出现"工作内存数据不一致"(如线程 A 修改变量后未写回,线程 B 仍读取旧值)。

三、JMM 的核心保障机制

JMM 通过以下机制保证原子性、可见性和有序性:

1. ​​volatile​​ 关键字
  • 可见性 :线程修改 ​volatile​ 变量后,会立即写回主内存;其他线程读取时,会强制从主内存加载最新值( invalidate 本地缓存)。
  • 有序性 :禁止 ​volatile​ 变量前后的指令重排序(通过内存屏障实现)。
  • ❌ 不保证原子性:例 ​volatile int i = 0; i++​ 仍可能出现并发问题(需结合原子类或锁)。
2. ​​synchronized​​ 关键字
  • 原子性:保证同步块内的操作是原子的(同一时间只有一个线程执行)。
  • 可见性:线程释放锁时,会将工作内存的修改写回主内存;线程获取锁时,会从主内存加载最新变量值。
  • 有序性:同步块内的指令禁止重排序(锁的获取和释放相当于内存屏障)。
3. ​​final​​ 关键字
  • 可见性​final​ 变量初始化后,其值不能被修改(基本类型)或引用不能被重新赋值(引用类型),且初始化完成后立即对其他线程可见(禁止重排序初始化过程)。
  • 例:​final int a = 10;​ 其他线程读取 ​a​ 时,一定能看到 ​10​,不会出现"半初始化"状态。
4. ​​happens-before​​ 规则(JMM 的核心)

​happens-before​​ 是 JMM 定义的 先行发生关系 ,用于判断多线程操作的可见性和有序性。若操作 A ​​happens-before​​ 操作 B,则 A 的结果对 B 可见,且 A 的执行顺序在 B 之前。

常用 ​​happens-before​​ 规则:

  1. 程序顺序规则 :同一线程内,代码按编写顺序执行(前序操作 ​happens-before​ 后续操作)。
  2. volatile 规则​volatile​ 变量的写操作 ​happens-before​ 后续的读操作。
  3. 锁规则 :锁的释放操作 ​happens-before​ 后续的获取锁操作。
  4. 线程启动规则​Thread.start()​ ​happens-before​ 线程内的任何操作。
  5. 线程终止规则 :线程内的所有操作 ​happens-before​ 线程的终止检测(如 ​Thread.join()​ 完成)。

四、JMM 的实际应用场景

JMM 是并发编程的基础,所有 Java 并发工具(如 ​​ThreadPoolExecutor​​、​​ConcurrentHashMap​​)都依赖 JMM 保证线程安全。

典型场景:
  1. 状态标志位 :用 ​volatile boolean flag​ 作为线程中断或状态切换的标志(保证可见性和有序性)。
  2. 单例模式 :双重检查锁单例(​volatile​ 禁止实例初始化的重排序,避免拿到"半初始化"对象)。
csharp 复制代码
public class Singleton {
    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;
    }
}
  1. 并发数据更新 :用 ​synchronized​​Lock​ 保证多线程更新共享变量的原子性和可见性。

总结

JMM 的核心是 解决多线程并发时的内存可见性、原子性和有序性问题 ,通过抽象主内存和工作内存的交互,以及 ​​volatile​​、​​synchronized​​、​​final​​ 和 ​​happens-before​​ 规则,为 Java 并发编程提供统一的内存模型规范。理解 JMM 是掌握并发编程的基础,也是面试中的高频考点。

相关推荐
南雨北斗26 分钟前
kotlin中的继承和委托
后端
白露与泡影29 分钟前
Spring Boot 4.0 发布总结:新特性、依赖变更与升级指南
java·spring boot·后端
狂奔小菜鸡31 分钟前
Day15 | Java内部类详解
java·后端·java ee
稚辉君.MCA_P8_Java36 分钟前
DeepSeek Java 插入排序实现
java·后端·算法·架构·排序算法
Cyan_RA943 分钟前
操作系统面试题 — Linux中如何查看某个端口有没有被占用?
linux·后端·面试
悟空码字1 小时前
Spring Boot 整合 Elasticsearch 及实战应用
java·后端·elasticsearch
JienDa1 小时前
PHP与八字命理的跨次元对话:当代码遇见命运的量子纠缠
后端
BingoGo1 小时前
PHP 8.5 在性能、调试和运维方面的新特性
后端·php
sino爱学习1 小时前
Guava 常用工具包完全指南
java·后端