一、先搞懂:JMM 到底是什么?(核心定义+比喻)
Java 内存模型不是指 JVM 的堆、栈、方法区这些内存区域(那是JVM内存结构),而是:
JMM 是一套抽象的规则/规范 ,定义了多线程环境下,线程如何访问共享内存、数据如何在主内存和线程私有内存之间同步,核心目的是解决多线程并发时的内存可见性、原子性、有序性问题,保证并发编程的正确性。
前端友好的比喻:
把 JVM 的共享内存比作"公司公共文件柜"(主内存),每个线程比作"员工",员工有自己的"私人笔记本"(工作内存):
- 员工不能直接修改文件柜里的文件,必须先把文件拷贝到自己的笔记本上修改,改完再写回文件柜;
- 如果多个员工同时操作同一个文件,就可能出现"你改的我看不到""改乱了""改的顺序不对"的问题;
- JMM 就是这套"文件操作规则":规定员工什么时候必须把笔记本的修改写回文件柜、什么时候必须读取最新的文件、不能乱改顺序等。
二、JMM 的核心结构:主内存 vs 工作内存
这是理解 JMM 的基础,和前端的"共享内存/私有内存"逻辑相通,但 Java 有明确规范:
| 内存类型 | 作用 | 对应数据 |
|---|---|---|
| 主内存(Main Memory) | 所有线程共享的内存区域,存储所有共享变量(实例变量、静态变量等) | 类实例、静态变量、数组 |
| 工作内存(Working Memory) | 每个线程独有的内存区域,存储线程私有的数据,以及主内存变量的拷贝 | 线程局部变量、主内存变量副本 |
核心规则:
线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存:
- 读取变量:从主内存拷贝到工作内存;
- 修改变量:在工作内存修改后,写回主内存;
- 问题根源:如果线程修改后没及时写回,或其他线程没读取最新值,就会出现并发问题。
三、JMM 要解决的 3 个核心并发问题(全栈开发必懂)
前端也会遇到并发(比如 Web Worker、异步请求),但 Java 多线程的并发问题更突出,JMM 就是为了约束这些问题:
1. 可见性问题:一个线程修改了变量,其他线程看不到
- 问题场景:线程 A 修改了主内存变量
flag=true,但没及时写回主内存,线程 B 还在读旧值flag=false; - JMM 解决方案:
volatile关键字(核心)、synchronized、Lock; - 代码示例(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 解决方案:
synchronized、Lock、原子类(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 全栈,你不需要深入底层硬件实现,但要知道:
- 写并发接口(比如秒杀、库存扣减)时,必须用
volatile/synchronized/Atomic类保证数据安全; - 分布式系统中,JMM 是本地并发的基础,分布式锁(Redis/ZooKeeper)是 JMM 锁机制的延伸;
- 面试必问:volatile 的作用、synchronized 和 volatile 的区别、JMM 三大特性。
总结
- 核心定义:JMM 是多线程访问共享内存的规则规范,解决可见性、原子性、有序性问题;
- 核心结构:主内存(共享)+ 工作内存(线程私有),线程操作变量需通过工作内存同步;
- 核心手段 :
volatile(可见性、有序性)、synchronized(全特性)、原子类(原子性)。
这些是 Java 并发编程的基础,也是全栈开发中处理后端并发请求的核心知识点,先掌握这些实用内容,再逐步深入底层即可。