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

一、先搞懂:JMM 到底是什么?(核心定义+比喻)

Java 内存模型不是指 JVM 的堆、栈、方法区这些内存区域(那是JVM内存结构),而是:

JMM 是一套抽象的规则/规范 ,定义了多线程环境下,线程如何访问共享内存、数据如何在主内存和线程私有内存之间同步,核心目的是解决多线程并发时的内存可见性、原子性、有序性问题,保证并发编程的正确性。

前端友好的比喻:

把 JVM 的共享内存比作"公司公共文件柜"(主内存),每个线程比作"员工",员工有自己的"私人笔记本"(工作内存):

  • 员工不能直接修改文件柜里的文件,必须先把文件拷贝到自己的笔记本上修改,改完再写回文件柜;
  • 如果多个员工同时操作同一个文件,就可能出现"你改的我看不到""改乱了""改的顺序不对"的问题;
  • JMM 就是这套"文件操作规则":规定员工什么时候必须把笔记本的修改写回文件柜、什么时候必须读取最新的文件、不能乱改顺序等。

二、JMM 的核心结构:主内存 vs 工作内存

这是理解 JMM 的基础,和前端的"共享内存/私有内存"逻辑相通,但 Java 有明确规范:

内存类型 作用 对应数据
主内存(Main Memory) 所有线程共享的内存区域,存储所有共享变量(实例变量、静态变量等) 类实例、静态变量、数组
工作内存(Working Memory) 每个线程独有的内存区域,存储线程私有的数据,以及主内存变量的拷贝 线程局部变量、主内存变量副本
核心规则:

线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存:

  1. 读取变量:从主内存拷贝到工作内存;
  2. 修改变量:在工作内存修改后,写回主内存;
  3. 问题根源:如果线程修改后没及时写回,或其他线程没读取最新值,就会出现并发问题。

三、JMM 要解决的 3 个核心并发问题(全栈开发必懂)

前端也会遇到并发(比如 Web Worker、异步请求),但 Java 多线程的并发问题更突出,JMM 就是为了约束这些问题:

1. 可见性问题:一个线程修改了变量,其他线程看不到
  • 问题场景:线程 A 修改了主内存变量 flag=true,但没及时写回主内存,线程 B 还在读旧值 flag=false
  • JMM 解决方案:volatile 关键字(核心)、synchronizedLock
  • 代码示例(volatile 解决可见性):
java 复制代码
public class VisibilityDemo {
    // 加 volatile,保证修改后立即刷新到主内存,其他线程能看到最新值
    private volatile boolean flag = false;

    public void changeFlag() {
        flag = true; // 线程1修改:立即写回主内存
        System.out.println("线程1修改flag为true");
    }

    public void readFlag() {
        // 线程2循环读取:如果不加volatile,会一直读工作内存的旧值false
        while (!flag) { 
            // 空循环
        }
        System.out.println("线程2读到flag为true,退出循环");
    }

    public static void main(String[] args) {
        VisibilityDemo demo = new VisibilityDemo();
        // 线程2:读取flag
        new Thread(demo::readFlag).start();
        
        // 延迟1秒,让线程2先启动
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        // 线程1:修改flag
        new Thread(demo::changeFlag).start();
    }
}
  • 效果:加 volatile 后,线程2能立即读到线程1修改的 flag=true,退出循环;不加则会无限循环。
2. 原子性问题:一个操作(或多个操作)要么全执行,要么全不执行
  • 问题场景:多线程对 count++ 操作(实际是"读取-修改-写回"3步),可能出现值被覆盖;
  • JMM 解决方案:synchronizedLock、原子类(AtomicInteger);
  • 核心:volatile 不保证原子性,只能保证可见性和有序性。
3. 有序性问题:代码执行顺序和编写顺序不一致(指令重排)
  • 问题场景:JVM/CPU 为了优化性能,会重排指令(比如 int a=1; int b=2; 可能先执行 b=2),单线程没问题,多线程会出错;
  • 典型例子:双重检查锁的单例模式(不加 volatile 会因指令重排创建多个实例);
  • JMM 解决方案:volatile(禁止指令重排)、synchronized
  • 代码示例(volatile 禁止指令重排):
java 复制代码
// 单例模式:双重检查锁,必须加volatile禁止指令重排
public class Singleton {
    // 加volatile:禁止instance = new Singleton()的指令重排
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    // 不加volatile:这行代码会被拆成3步,重排后可能导致其他线程读到未初始化的instance
                    instance = new Singleton(); 
                }
            }
        }
        return instance;
    }
}

四、JMM 在全栈开发中的实际应用

作为前端转 Java 全栈,你不需要深入底层硬件实现,但要知道:

  1. 写并发接口(比如秒杀、库存扣减)时,必须用 volatile/synchronized/Atomic 类保证数据安全;
  2. 分布式系统中,JMM 是本地并发的基础,分布式锁(Redis/ZooKeeper)是 JMM 锁机制的延伸;
  3. 面试必问:volatile 的作用、synchronized 和 volatile 的区别、JMM 三大特性。

总结

  1. 核心定义:JMM 是多线程访问共享内存的规则规范,解决可见性、原子性、有序性问题;
  2. 核心结构:主内存(共享)+ 工作内存(线程私有),线程操作变量需通过工作内存同步;
  3. 核心手段volatile(可见性、有序性)、synchronized(全特性)、原子类(原子性)。

这些是 Java 并发编程的基础,也是全栈开发中处理后端并发请求的核心知识点,先掌握这些实用内容,再逐步深入底层即可。

相关推荐
量子炒饭大师2 小时前
【C++入门】Cyber神经的义体插件 —— 【类与对象】内部类
java·开发语言·c++·内部类·嵌套类
Hx_Ma162 小时前
测试题(四)
java·开发语言·jvm
Never_Satisfied2 小时前
在c#中,抛出异常,并指定其message的值
java·javascript·c#
多打代码2 小时前
2026.02.11
开发语言·python
没有bug.的程序员2 小时前
IDEA 效能巅峰实战:自定义模板 Live Templates 内核、快捷键精密逻辑与研发提效深度指南
java·ide·intellij-idea·快捷键·研发提效·自定义模板
lly2024062 小时前
Scala IF...ELSE 语句详解
开发语言
追随者永远是胜利者2 小时前
(LeetCode-Hot100)22. 括号生成
java·算法·leetcode·职场和发展·go
逝水如流年轻往返染尘2 小时前
java中的泛型
java
百锦再2 小时前
Java重入锁(ReentrantLock)全面解析:从入门到源码深度剖析
java·开发语言·struts·spring·kafka·tomcat·intellij-idea