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

相关推荐
雨辰AI1 分钟前
openGauss 生产运维避坑指南|适配信创项目改造核心难点
java·运维·后端
归去来?17 分钟前
Python 常用优雅写法
开发语言·python
名字还没想好☜19 分钟前
Java 21 switch 模式匹配实战:sealed 接口 + record 替代 if-instanceof 链
java·人工智能·后端·python·spring
devilnumber28 分钟前
Java 30 组高频技术 / 知识点多角度对比
java·开发语言
摇滚侠33 分钟前
《SpringBoot 3:入门与应用实战》第 6 章 Spring Boot 最佳实践 阅读笔记 10
java·spring boot·笔记
何以解忧,唯有..36 分钟前
Python 异常处理完全指南:从基础到高级实践
开发语言·前端·python
gugucoding1 小时前
57. 【Java】日志框架:SLF4J与Logback
java·开发语言·logback
今天的砖头有点烫手啊1 小时前
JVM 调优实战:从 GC 日志到参数优化,一次完整排查
java·jvm
此生决int1 小时前
深入理解C++系列(15)——AVL树
开发语言·c++
筱璦1 小时前
PHP+ VUE3机构级股票分仓管理交易系统源码可出
开发语言·php·vue3·量化·股票分仓