Java 内存模型(Java Memory Model, JMM)是 Java 虚拟机(JVM)规范中定义的一种抽象模型,用于描述多线程环境下,线程如何与内存交互,以及如何保证线程之间的可见性、有序性和原子性。JMM 是 Java 并发编程的基础,它定义了线程如何访问共享变量,以及如何通过同步机制(如 volatile
、synchronized
等)来协调线程之间的操作。
Java 内存模型的核心概念
1. 主内存与工作内存
- 主内存(Main Memory):
- 主内存是所有线程共享的内存区域,存储了所有的变量(实例字段、静态字段等)。
- 工作内存(Working Memory):
- 每个线程都有自己的工作内存,工作内存是线程私有的,存储了线程对主内存中变量的副本。
- 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接操作主内存。
2. 内存间的交互操作
JMM 定义了以下 8 种操作来完成主内存与工作内存之间的交互:
- lock(锁定):作用于主内存的变量,标识一个变量为线程独占状态。
- unlock(解锁):作用于主内存的变量,释放一个被锁定的变量。
- read(读取):从主内存中读取变量的值到工作内存。
- load(载入):将 read 操作读取的值放入工作内存的变量副本中。
- use(使用):将工作内存中的变量值传递给执行引擎(如 CPU)。
- assign(赋值):将执行引擎计算的结果赋值给工作内存中的变量。
- store(存储):将工作内存中的变量值传送到主内存。
- write(写入):将 store 操作传送的值写入主内存的变量中。
这些操作需要满足一定的规则,例如:
- read 和 load、store 和 write 必须成对出现。
- 不允许一个线程丢弃最近的 assign 操作(即变量在工作内存中改变了之后必须同步到主内存)。
- 不允许一个线程无原因地(没有发生过任何 assign 操作)把数据从工作内存同步回主内存。
3. 可见性(Visibility)
- 可见性是指一个线程对共享变量的修改,能够及时被其他线程看到,它通过内存屏障(Memory Barrier)实现。
- JMM 通过以下机制保证可见性:
volatile
关键字:确保变量的修改对所有线程立即可见。synchronized
关键字:在释放锁之前,会将工作内存中的变量同步到主内存。final
关键字:确保变量在构造完成后对其他线程可见。
内存屏障:
写屏障(Store Barrier)
:确保当前线程的写操作(对主内存的写操作)在此屏障之前完成,(它会立刻把这个修改同步到主内存中)。
读屏障(Load Barrier)
:确保当前线程的读操作在此屏障之后完成。
在 volatile 变量上操作时,JVM 会插入 写屏障 和 读屏障,以确保变量在不同线程之间的可见性。这样,当一个线程更新了 volatile 变量,另一个线程立刻能看到更新。
4. 有序性(Ordering)
- 有序性是指程序执行的顺序按照代码的先后顺序执行。
- JMM 通过以下机制保证有序性:
volatile
关键字:禁止指令重排序,确保变量的读写操作按顺序执行。synchronized
关键字:确保同一时刻只有一个线程执行同步代码块。happens-before
规则:定义了一些操作之间的先后顺序,确保前一个操作的结果对后一个操作可见。
5. 原子性(Atomicity)
- 原子性是指一个操作是不可分割的,要么全部执行成功,要么全部不执行。
- JMM 通过以下机制保证原子性:
synchronized
关键字:确保同步代码块的原子性。java.util.concurrent.atomic
包:提供了一些原子类(如AtomicInteger
),通过 CAS(Compare-And-Swap)操作保证原子性。
happens-before
规则
happens-before
是 JMM 的核心规则之一,用于定义操作之间的可见性和顺序。以下是常见的 happens-before
规则:
- 程序顺序规则:在一个线程中,前面的操作 happens-before 后面的操作。
volatile
规则:对一个volatile
变量的写操作 happens-before 后续对这个变量的读操作。- 锁规则:解锁操作 happens-before 后续的加锁操作。
- 线程启动规则:线程的
start()
方法 happens-before 该线程的任何操作。 - 线程终止规则:线程的所有操作 happens-before 其他线程检测到该线程已经终止。
- 传递性规则:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
Java 内存模型的作用
- 屏蔽硬件差异:
- JMM 定义了统一的内存模型,屏蔽了不同硬件和操作系统在内存访问上的差异。
- 保证线程安全:
- 通过
volatile
、synchronized
等机制,确保多线程环境下的可见性、有序性和原子性。
- 通过
- 优化编译器指令重排序:
- JMM 允许编译器和处理器对指令进行重排序,但必须遵守
happens-before
规则,确保程序执行结果的正确性。
- JMM 允许编译器和处理器对指令进行重排序,但必须遵守
示例:volatile
关键字的作用
public class VisibilityExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
while (!flag) { // 读操作
// 等待
}
System.out.println("Flag is true");
}
}
- 如果没有
volatile
,reader()
方法可能永远看不到flag
的变化。 - 使用
volatile
后,flag
的修改对所有线程立即可见。
总结
Java 内存模型(JMM)是 Java 并发编程的核心,它定义了多线程环境下线程如何与内存交互,并通过 happens-before
规则、volatile
、synchronized
等机制保证了可见性、有序性和原子性。理解 JMM 对于编写高效、线程安全的并发程序至关重要。