Java 内存模型(Java Memory Model,简称 JMM)是 Java 规范的一部分,用于定义线程之间如何通过内存共享变量进行交互。JMM 在多线程环境中确保变量的可见性、原子性和有序性。
1. 什么是 JMM?
JMM 是 Java 的一部分,用于屏蔽不同硬件和操作系统的内存访问差异,保证 Java 程序在不同平台上的一致性。JMM 规范了变量在内存中的存储和读取行为,并通过一系列规则来确保多线程程序的正确性。
2. JMM 的三大特性
2.1 可见性
可见性是指一个线程对共享变量的修改,能够及时地被其他线程看到。JMM 通过以下机制保证可见性:
- volatile 关键字:保证被 volatile 修饰的变量在被修改后立即刷新到主内存,并且从主内存读取。
- synchronized 块:释放锁时会将变量的修改刷新到主内存,获取锁时会从主内存加载最新的变量值。
- final字段:被final修饰的字段在构造器中一旦被初始化,其他线程就能看到它的最新值。
2.2 原子性
原子性是指某个操作是不可分割的,即使在多线程环境中也不会被打断。JMM 保证以下操作的原子性:
- 基本数据类型的读取和赋值(long 和 double 除外)。
- 使用 synchronized 锁定的代码块。
2.3 有序性
有序性是指程序执行的顺序按照代码的先后顺序执行。JMM 通过以下机制保证有序性:
- happens-before 规则:JMM 提供了 happens-before 规则来确保操作之间的顺序,例如:
- 程序次序规则:一个线程内,按照代码顺序执行。
- 锁定规则:unlock 操作先行发生于 lock 操作。
- volatile 变量规则:对 volatile 变量的写操作先行发生于对该变量的读操作。
- 线程启动规则:Thread.start() 先行发生于该线程的每一个动作。
- 线程终止规则:线程中的所有操作先行发生于该线程的终止检测(Thread.join() 方法)。
内存屏障
JMM通过内存屏障来禁止特定类型的重排序,确保内存可见性和有序性。内存屏障有以下几种:
- LoadLoad屏障:禁止在屏障前的所有加载操作重排序到屏障之后。
- StoreStore屏障:禁止在屏障后的所有存储操作重排序到屏障之前。
- LoadStore屏障:禁止在屏障前的所有加载操作重排序到屏障之后。
- StoreLoad屏障:禁止在屏障后的所有存储操作重排序到屏障之前。
3. JMM 的工作原理
JMM 规范了线程和主内存之间的交互。每个线程都有自己的工作内存(本地内存),工作内存保存了该线程使用的变量的副本(从主内存中拷贝而来)。线程对变量的所有操作都在工作内存中进行,变量的值只有在刷新到主内存后,其他线程才能看到。
3.1 主内存与工作内存
- 主内存:所有共享变量(实例变量、静态变量)的存储区域。
- 工作内存:每个线程的本地内存区域,保存了该线程使用的变量的副本。
3.2 交互操作
线程与主内存之间的交互通过以下八种操作完成:
- lock:作用于主内存变量,把一个变量标识为线程独占状态。
- unlock:作用于主内存变量,释放一个被线程独占的变量。
- read:作用于主内存变量,把一个变量的值从主内存传输到线程的工作内存中。
- load:作用于工作内存变量,把 read 操作从主内存得到的变量值放入工作内存的变量副本中。
- use:作用于工作内存变量,把工作内存中的变量值传递给执行引擎。
- assign:作用于工作内存变量,把执行引擎接收到的值赋给工作内存的变量。
- store:作用于工作内存变量,把工作内存的变量值传送到主内存中。
- write:作用于主内存变量,把 store 操作从工作内存得到的变量值放入主内存的变量中。
4. 示例代码
以下是一个简单的示例,演示 volatile 关键字的可见性保证:
在这个示例中,
volatile
关键字保证了flag
变量的可见性,
readerThread
能够及时看到writerThread
对flag
的修改。
public class VolatileExample {
private static volatile boolean flag = false;
public static void main(String[] args) {
Thread writerThread = new Thread(() -> {
try {
Thread.sleep(1000); // 模拟一些工作
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("Flag set to true");
});
Thread readerThread = new Thread(() -> {
while (!flag) {
// busy-waiting
}
System.out.println("Flag detected as true");
});
writerThread.start();
readerThread.start();
}
}