Java 内存模型(Java Memory Model,JMM)是 Java 语言规范中的一套规则,它描述了多线程环境下的线程与内存(主内存和高速缓存)的交互方式,以保证可见性,有序性和原子性,同时它屏蔽了硬件与操作系统的底层差异,使得 Java 程序在所有平台下的内存访问效果一致。
我们知道:CPU > 内存 , CPU的运算速度是远大于内存的,于是为了减少差异,CPU引入了L1、L2、L3,在多线程中,各线程可能运行在不同的核上使用自己的主内存拷贝副本数据,如果两个线程Thread1跟Thread2拷贝同一个内存块的数据,那么如果Thread1先于Thread2,Thread2是无法感知Thread1修改线程的,因为已经拷贝到cache中了

上下文切换带来的原子性问题
Java 中常常会使用count++
的方式来实现计数器的自增操作,直觉上我们认为该操作是“一气呵成”的,但实际上对应的计算机中执行了 3 条指令:
- 指令1:将count读入缓存;
- 指令2:执行自增操作;
- 指令3:将自增后的count写入内存。
如果运行在同一个核心上的线程 Thread1 和线程 Thread2 先后执行count++
,可能会存在一种情况:

初始状态下 count 为 0, 我们期望执行结束后线程 Thread1 的执行结果是 1,线程 Thread2 的执行结果是 2,但实际上恰恰相反,这就是上下文切换带来的原子性问题。
指令重排带来的有序性问题
指令重排是 CPU 一项重要的优化手段,在不改变单线程执行结果的前提下,CPU 可以自行选择如何优化指令。指令重排遵循两个基本原则:
- 数据依赖原则:如果两个操作使用的数据存在依赖性,那么不能通过指令重排来优化这两个操作的执行顺序;
- as-if-serial 语义:无论如何重排序,都要保证单线程场景下的语义不能被改变(或者说执行结果不变)。
我们举个 Java 中经典的例子,未正确同步的单例模式:
public static class Singleton {
private Singleton instance;
public Singleton getInstance() {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {
}
}
Java 中通过关键字 new 来创建一个对象要经历 3 步:
- 为这个对象分配内存;
- 初始化这块内存;
- 将变量名指向这块内存。
分析数据依赖原则,操作 1 是要先于操作 2 和操作 3 执行的,操作 2 和操作 3 之间并没有依赖性,如果操作 2 和操作 3 交换了执行顺序,依旧满足单线程环境下的语义,因此,在实际的执行过程中,无论是 1 -> 2 -> 3 还是 1->3->2 都是可以接受的。
那么在这个例子中,可能出现如下情况:

这种情况下线程 Thread2 拿到的是未经过初始化的 instance 对象。
JMM 的理解
JMM 提供了一系列 Java 内存交互规范,用于规范不同平台下多线程环境中并发访问共享内存的方式,以保证可见性,原子性和有序性,确保程序的可预测性和可靠性。JMM 中最重要的两个部分是 as-if-serial 语义和 Happens-Before 原则。
Happens-Before 原则是 JMM 中的一部分,用于描述多线程环境下操作之间执行结果的顺序,例如:操作 A happens-before 操作 B(记作�ℎ�→�AhbB),表示无论在何种情况下,操作 A 的结果对操作 B 来说都是可见的。
JSR-133 中定义了 Happens-Before 原则的 6 项内容:
- 程序顺序规则:线程中的每个操作happens-before该线程中的任意后续操作。
- 监视器锁规则:锁的解锁happens-before随后这个锁的加锁。
- volatile变量规则:volatile变量的写happens-before后续任意对这个volatile变量的读。
- 传递性:如果ℎ→AhbB,且ℎ→BhbC,那么ℎ→AhbC。
Thread#start
规则:如果线程 t1 执行操作启动线程 t2(执行t2.start
),那么 t1 线程的t2.start
操作happens-before于线程 t2 中的任意操作。Thread# join
规则:如果线程 t1 执行操作t2.join
并成功返回,那么线程B中的任意操作happens-before于线程A从t2.join
操作成功返回。
Happens-Before 原则提供了线程间的可见性保证,描述了线程执行结果间的先后顺序,但 Happens-Before 原则并不限制指令执行的顺序,即Happens-Before 原则并不禁止重排序,只是要求重排序后的结果满足Happens-Before 原则的要求。