1、处理器内存模型

顺序一致性内存模型是一个理论参考模型,JMM 和处理器内存模型在设计时通常会以顺序一致性内存模型为参考。在设计时,JMM 和处理器内存模型会对顺序一致性模型做放松,因为如果完全按照顺序一致性来实现处理器和 JMM,那么很多处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响。

根据对不同类型的读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为如下几种类型

  1. 放松程序中写-读操作的顺序,Total Store Ordering 内存模型,简称 TSO
  2. 在第一个基础上,继续放松写-写操作的顺序,Partial Store Ordering 内存模型,简称 PSO
  3. 在前面两条的基础上,继续放松读-写和读-读操作的顺,Relaxed Memory Ordering 内存模型(简称 RMO)和 PowerPC 内存模型

注意: 这里处理器对读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的。因为处理器要遵守 as-if-serial 语义,处理器不会对存在数据依赖性的两个内存操作做重排序。

常见处理器内存模型的细节如表所示,从表中可以看到,所有处理器内存模型都允许写-读重排序,原因是现在处理器都是用了写缓冲区,而写缓冲区可能导致写-读操作重排序。同时,这些处理器内存模型都允许更早读到当前处理器的写,原因同样是写缓冲区,由于写缓冲区仅对当前处理器可见,这个特性导致当前处理器可以比其他处理器先看到临时保存在自己缓冲区中的写。

内存模型 处理器 Store-Load 重排序 Store-Store 重排序 Load-Load 和 Load—Store 重排序 可以更早读取到其他处理器的写 可以更早读取到当前处理器的写
TSO sparc-TSO X86 Y N N N Y
PSO sparc-PSO Y Y N N Y
RMO ia64 Y Y Y N Y
PowerPC PowerPC Y Y Y Y Y

表中的处理器模型,从上到下,模型由强到弱。越是追求性能的处理器,内存模型设计地会越弱。因为这些处理器内存模型对它们的束缚越少越好,这样它们就可以做尽肯能多的优化来提高性能。

由于常见的处理器内存模型比 JMM 要弱,Java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时,由于各种处理器内存模型的强弱不同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM 在不同的处理器中需要插入内存屏障的数量和种类也不相同,如图所示:

八、Java 内存模型综述 - 图1

JMM 屏蔽了不同处理器内存模型的差异,在不同的处理器上为 Java 程序员呈现了一个一致的内存模型。

2、各种内存模型之间的关系

JMM 是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型、处理器内存模型和顺序一致性内存模型的强弱对比图。

八、Java 内存模型综述 - 图2

从图中可以看出,常见的 4 种处理器内存模型比常用的 3 种语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。通处理器内存模型一样,于是追求性能的语言,内存模型设计的会越弱。

3、JMM 的内存可见性保证

按程序类型,Java 程序的内存可见性保证可以分为下列三类:

  1. 单线程程序:单线程程序不会出现内存可见性问题。编译器、runtime 和处理器会同时确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同
  2. 正确同步的线程程序:正确同步的多线程程序的执行将具有顺序一致性,程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。JMM 通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
  3. 未同步/未正确同步的多线程程序:JMM 为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认的值(0、null、false)

最小安全性保障与 64 位数据的非原子性写并不矛盾。它们都是不同的概念,它们发生的时间点也不相同。最小安全性保证对象默认初始化之后,才会被任意线程使用。最小安全性发生在对象被任意线程使用之前。64 位数据的非原子性发生在对象被多个线程使用的过程中(写共享变量)。

当发生问题时,处理器 B 看到仅仅被处理器 A 写了一半的无效值,这里虽然处理器 B 读取到一个被写了一半的无效值,但这个值仍然是处理器 A 写入的,只不过是处理器 A 还没有写完而已。最小安全性保证线程读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。但最小安全性保证并不保证线程读取到的值,一定是线程读取到的值一定是正确的。

如图所示,展示了这三类程序在 JMM 中与在顺序一致性内存模型中的执行结果的异同。只要多线程程序是正确同步的,JMM 保证该程序在任意的处理器上的执行结果与在顺序一致性内存模型中的执行结果一致

八、Java 内存模型综述 - 图3

4、JSR-133 对旧内存模型的修补

JSR-133 对 JDK5 之前的就内存模型的修补主要有两个:

  1. 增强 volatile 的内存语义:旧内存模型中,允许 volatile 变量与普通变量重排序,JSR-133 严格限制 volatile 变量与普通变量的重排序,使用 volatile 的写-读和锁的释放-获取具有相同的内存语义
  2. 增强 final 的内幕才能语义:旧内存模型中,多次读取同一个 final 变量的值可能会不相同,JSR-133 为 final 增加了两个重排序规则;在确保 final 引用不会从构造函数内逃逸的情况下,final 具有了初始化安全性。