Java 内存模型(JMM - Java Memory Model)
Java 内存模型(JMM - Java Memory Model)
1. 什么是 Java 内存模型(JMM)?
Java 内存模型(JMM,Java Memory Model)是 Java 虚拟机(JVM)对多线程访问内存的抽象定义,主要用于屏蔽不同 CPU 和操作系统的内存访问差异,保证 Java 代码在不同平台上能获得一致的并发语义。
JMM 主要解决 多线程环境下的可见性、有序性和原子性 问题。
2. JMM 关键概念
JMM 规定 Java 内存由 主内存(Main Memory) 和 工作内存(Working Memory) 组成:
- 主内存(Heap,Shared Memory):所有线程共享的存储区域(存放对象、静态变量等)。
- 工作内存(Thread Stack,Local Cache):每个线程私有的存储区域(存放局部变量、方法调用栈等)。
- 主内存与工作内存之间的数据同步 由 JMM 规范规定,包括变量的读取(load)、写入(store)和刷新(refresh)等规则。
3. JMM 三大特性
JMM 主要解决 可见性、原子性、有序性 三大问题:
特性 | 作用 | 解决方案 |
---|---|---|
原子性(Atomicity) | 保证操作不可被线程调度机制中断 | synchronized 、volatile 、Atomic 变量 |
可见性(Visibility) | 线程对变量的修改能被其他线程看到 | volatile 、synchronized 、final |
有序性(Ordering) | 代码执行顺序符合程序预期 | volatile 、synchronized 、Happens-Before 规则 |
4. Java 内存模型的 "Happens-Before" 规则
JMM 通过 Happens-Before 关系 规定多个操作的执行顺序,保证线程安全。关键规则包括:
- 程序顺序规则:单线程中,代码按书写顺序执行。
- 锁定规则(Monitor Lock Rule):对同一锁的
unlock
操作,必须发生在lock
之后。 - volatile 变量规则:对
volatile
变量的写入,Happens-Before 之后的读取,保证可见性。 - 线程启动规则(Thread Start Rule):
Thread.start()
Happens-Before 线程中的所有操作。 - 线程终止规则(Thread Join Rule):线程的所有操作 Happens-Before
Thread.join()
之后的操作。 - 中断规则(Thread Interrupt Rule):对
Thread.interrupt()
的调用 Happens-Before 线程检测到中断状态的代码。 - 对象终结规则(Finalizer Rule):对象的构造函数执行完成 Happens-Before 该对象
finalize()
方法的调用。 - 传递性(Transitivity):如果 A
Happens-Before
B,且 BHappens-Before
C,则 AHappens-Before
C。
5. JMM 解决并发问题的三种关键方式
1️⃣ volatile:保证可见性 & 防止指令重排序
volatile
关键字保证变量对所有线程的可见性。- 但不保证原子性,只能用于 单一写入、多线程读取 的场景。
volatile
还会 禁止指令重排序,保证一定的有序性。
示例:
class VolatileExample {
private static volatile boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
while (!flag) {} // 线程1 等待 flag 变为 true
System.out.println("Flag changed!");
}).start();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
flag = true; // 线程2 修改 flag,volatile 让线程1 可见
}
}
💡 volatile
适用于状态标志变量(如 isRunning
),但不能保证 i++
这样的复合操作的原子性。
2️⃣ synchronized:保证原子性 & 可见性 & 有序性
synchronized
保证代码块的原子性。- 进入
synchronized
代码块时,必须从主内存读取最新变量值,保证可见性。 - 退出
synchronized
代码块时,必须将修改后的变量值刷新回主内存,保证数据一致性。 - 还能保证代码块的有序执行,避免指令重排序。
示例:
class SyncExample {
private int count = 0;
public synchronized void increment() { // 线程安全
count++;
}
}
3️⃣ Lock & Atomic
ReentrantLock
代替synchronized
,支持公平锁、可中断锁等高级功能。AtomicInteger
等java.util.concurrent.atomic
包提供的类使用 CAS + volatile 变量,比synchronized
更高效。
示例(AtomicInteger
保证原子性):
import java.util.concurrent.atomic.AtomicInteger;
class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作
}
}
6. 指令重排序(Instruction Reordering)
JVM 为了提高性能,可能会对指令进行优化重排序,导致执行顺序与代码顺序不同。
- 单线程环境下无影响。
- 多线程环境下可能导致不可预测的并发问题。
示例:
class ReorderExample {
int a = 0, b = 0, x = 0, y = 0;
public void method1() {
a = 1; // ①
x = b; // ②
}
public void method2() {
b = 1; // ③
y = a; // ④
}
}
可能的执行顺序:
- ①②③④(符合书写顺序)
- ③④①②(由于 CPU 重排序)
- ①③②④(部分重排序,导致
x = 1, y = 0
)
🔹 解决方案:
volatile
防止重排序(store-load
屏障)。synchronized
/Lock
确保代码执行顺序。
🔹 总结
关键点 | 作用 | 解决方案 |
---|---|---|
JMM | 线程间内存交互规则 | 规定工作内存 & 主内存交互 |
Happens-Before | 线程执行顺序 | 保证 JMM 可见性 & 有序性 |
原子性 | 线程安全性 | synchronized 、Atomic |
可见性 | 保证线程读取最新值 | volatile 、synchronized |
有序性 | 防止指令重排序 | volatile 、synchronized |
指令重排序 | 性能优化可能导致并发问题 | volatile 变量屏障 |
本文作者:MuXinu
本文链接:https://www.cnblogs.com/MuXinu/p/18711907
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步