java内存模型JMM

Java 内存模型(JMM)

Java 内存模型(JMM)是一套规范,用来屏蔽不同硬件和操作系统的内存访问差异,让 Java 程序在各种平台下都能实现一致的并发效果。它的核心目标是解决多线程下的原子性、可见性、有序性问题,定义了线程和主内存之间的抽象关系。

一、JMM 核心:三大特性

JMM 围绕原子性、可见性、有序性三大特性设计,这是多线程并发安全的基础。

1. 原子性(Atomicity)

定义:一个操作是不可中断的,要么全部执行成功,要么全部不执行,执行过程中不会被其他线程打断。

  • 基本数据类型的赋值 / 读取是原子操作(如 int a=1;b=a;)。
  • 复合操作不是原子性(如 i++,本质是「读取 - 修改 - 写入」三步)。

保证原子性的方式

  • synchronized(互斥锁,强制操作串行执行)。
  • java.util.concurrent.atomic 包下的原子类(CAS 无锁算法)。

2. 可见性(Visibility)

定义:当一个线程修改了共享变量的值,其他线程能立即感知到这个修改。

  • JMM 规定:所有共享变量存在主内存,每个线程有自己的工作内存(线程私有)。
  • 线程操作变量:先从主内存拷贝到工作内存 → 修改 → 写回主内存。
  • 可见性问题:线程 A 修改了变量,还没写回主内存,线程 B 读取的是旧值。

保证可见性的方式

  • volatile(轻量级,强制直接读写主内存)。
  • synchronized(解锁前必须将变量刷回主内存)。
  • final(修饰的变量初始化后不可修改,天然可见)。

3. 有序性(Ordering)

定义:程序执行的顺序按照代码的先后顺序执行。

  • 单线程:天然有序(as-if-serial 语义,不管怎么重排,结果不变)。
  • 多线程:指令重排会破坏有序性,导致线程间执行顺序混乱。

保证有序性的方式

  • volatile(禁止指令重排)。
  • synchronized(同一时刻只有一个线程执行,无重排问题)。
  • Happens-Before 原则(JMM 定义的有序性规则,无需手动加锁)。

二、volatile 原理与使用场景

volatile 是 JMM 提供的轻量级同步机制,只保证可见性 + 有序性不保证原子性

1. 核心原理

  1. 可见性实现:

    volatile修饰的变量,线程修改后立即刷回主内存,其他线程读取时直接从主内存加载,跳过工作内存。

  2. 有序性实现:

    插入内存屏障,禁止编译器和 CPU 进行指令重排

2. 关键特性

  • 不保证原子性:volatile int i; i++ 依然线程不安全。
  • 轻量级:无锁,不会阻塞线程(比 synchronized 高效)。

3. 最佳使用场景

  1. 状态标志位(单一写、多读)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    volatile boolean flag = false;
    // 线程A 修改标志
    public void stop() {
    flag = true;
    }
    // 线程B 感知标志
    public void run() {
    while (!flag) {
    // 执行业务
    }
    }
  2. 单例模式双重校验锁(DCL)

    必须用volatile禁止指令重排,防止对象半初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Singleton {

    // 关键点:必须加 volatile
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
    // 第一次判断:避免每次都加锁,提升效率
    if (instance == null) {
    synchronized (Singleton.class) {
    // 第二次判断:防止多线程同时进入第一层if
    if (instance == null) {
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }

    instance = new Singleton() 这一行代码,不是原子的,CPU 会拆成 3 步:

    1. 分配内存空间

      在堆上开辟一块内存,此时对象是默认零值(未初始化)。

    2. 初始化对象

      调用构造方法,给成员变量赋值,对象真正 “建好”。

    3. 将 instance 引用指向这块内存

      instance = 内存地址

    如果不加volatile关键字,多线程下:

    线程 A 进入 synchronized 里:

    • 执行 new Singleton()

    • 走到步骤 3:instance = 地址

      → 此时 instance 已经!= null

    • 但还没执行步骤 2:对象构造方法还没跑,字段都是默认值

    这时候线程 B 来了:

    1
    if (instance == null)

    判断结果是 false

    → 直接 return instance

    → 线程 B 拿到了一个存在、但没初始化完的对象

    后果:

    线程 B 去使用这个对象

    → 访问成员变量全是默认值(0 /null/false)

    → 空指针、状态错乱、程序诡异崩溃

    加 volatile 后

    volatile 禁止指令重排

    保证顺序永远是:

    1. 分配内存
    2. 初始化对象
    3. 引用赋值

    线程 B 只有两种可能:

    • 要么看到 instance == null
    • 要么看到 完全初始化好的对象

    绝对看不到半初始化的中间状态。


三、指令重排与内存屏障

1. 指令重排

定义:编译器 / CPU 为了优化性能,在不改变单线程执行结果的前提下,调整代码指令的执行顺序。

  • 单线程:安全,无影响。
  • 多线程:不安全,会导致线程间数据错乱。

重排类型

  1. 编译器重排(编译阶段)。
  2. 处理器重排(运行阶段)。

2. 内存屏障(Memory Barrier)

JMM 通过内存屏障解决指令重排和可见性问题,是 CPU 层面的指令。

四大屏障作用

  1. LoadLoad:禁止读操作重排。
  2. StoreStore:禁止写操作重排。
  3. LoadStore:禁止读→写重排。
  4. StoreLoad:禁止写→读重排(全能屏障,开销最大)。

volatile 的屏障策略

  • 写操作前 / 后插入屏障。

  • 读操作后插入屏障。

    → 最终效果:禁止 volatile 变量前后的指令重排。


四、堆、栈、方法区与线程的关系

这是Java 内存区域(运行时数据区),和 JMM 是不同维度的概念,但和线程强相关:

内存区域 存储内容 线程归属 核心特点
堆(Heap) 对象实例、数组 线程共享 GC 主要区域,OOM 高发区
虚拟机栈 局部变量表、方法栈帧 线程私有 生命周期与线程一致
本地方法栈 native 方法执行 线程私有 与虚拟机栈类似
程序计数器 当前线程执行的字节码行号 线程私有 唯一不会 OOM 的区域
方法区 类信息、常量、静态变量、即时编译代码 线程共享 JDK8 后叫元空间(Metaspace)

核心关系总结

  1. 线程私有区域:虚拟机栈、本地方法栈、程序计数器
    • 每个线程独立一份,无线程安全问题(不需要 JMM 保证)。
    • 方法执行时创建栈帧,方法结束自动释放内存。
  2. 线程共享区域:堆、方法区
    • 所有线程共用一块内存,存在线程安全问题(需要 JMM 保证三大特性)。
    • 共享变量(实例变量、静态变量)都存在这里,是并发问题的根源。

java内存模型JMM
http://hanqichuan.com/2026/04/15/java并发/java内存模型JMM/
作者
韩启川
发布于
2026年4月15日
许可协议