JUP JMM (七) JMM Summary


Java 内存模型总结

1、处理器的内存模型

顺序一致性内存模型是一个理论参考模型,JMM 和处理器内存模型在设计时通常会以顺序一致性模型作为参考。

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

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

  • 放松程序中写-读操作的顺序,由此产生 Total Store Ordering 内存模型(简称为 TSO);
  • 在上面的基础上,继续放松程序中写-写操作的顺序,由此产生了 Partial Store Order 内存模型(简称 PSO);
  • 在前面两条的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了 Relaxed Memory Order 内存模型(简称为 RMO)和 PowerPC 内存模型;

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

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

如上表所示,所有处理器内存模型都允许 写-读 重排序,因为它们都使用写缓存区。写缓存区可能导致写-读重排序。

同时可以看到这些处理器内存模型都允许更早读到当前处理器的写,这也是写缓存区导致的。由于写缓存区仅对当前处理器可见,这个特性会导致当前处理器比其他处理器更早地看到保存在自己写缓存区中写。

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

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

下图展示了 JMM 在不同处理器内存模型中需要插入的内存屏障:

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

JMM 是语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。

下面是语言内存模型、处理器内存模型和顺序一致性内存模型的强弱对比图:

从图中可以看出:常见的四种处理器内存模型比常用的三种语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。

和处理器内存模型一样,对于语言内存模型而言,越是追求执行性能的语言,内存模型就会设计就越弱。

3、JMM 的内存可见性保证

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

(1)单线程程序:单线程程序不会出现内存可见性问题。编译器、runtime 和处理器会共同确保单线程程序的执行结果和程序在顺序一致性模型中执行结果一致;

(2)正确同步的的多线程程序:正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是 JMM 关注的重点,JMM 通过限制编译器和处理器的重排序来为程序员提供内存可见性保证;

(3)未同步/未正确同步的多线程程序:JMM 未它们提供最小安全性保障:线程执行时读取到的值,要么是之前按某个线程写入的值,要么就是默认值(0、null、false)。

注意:最小安全性保障与 64 位数据的非原子性写并不矛盾。它们是两个不同的概念,它们 “发生” 的时间点也不同。

最小安全性保证

最小安全性保证对象默认初始化之后(设置成员域为 0、null 或 false),才会被任意线程使用。最小安全性 “发生” 在对象被任意线程使用之前。

64 位数据的非原子性写

64 位数据的非原子性写 “发生” 在对象被多个线程使用的过程中(写共享变量)。当发生问题时(处理器 B 看到仅仅被处理器 A “写了一半” 的无效值),这里虽然处理器 B 读取到一个写了一半的无效值,但这个值仍然是处理器 A 写入的,只不过是处理器 A 还没有写完而已。

最小安全性保证线程读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。但最小安全性并不保证线程读取到的值,一定是某个线程写完后的值。最小安全性保证线程读取到的值不会无中生有地冒出来,但并不保证线程读取到的值一定是正确的。

下图展示了这三类程序在 JMM 中与在顺序一致性模型中执行结果的异同:

只要多线程程序是正确同步的,JMM 就可以保证在任何处理器平台上的执行结果,与该程序在顺序一致性内存模型中的执行结果一致。

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

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

(1)增强 volatile 的内存语义。旧内存模型允许 volatile 变量与普通变量重排序。JSR-133 严格限制 volatile 变量与普通变量的重排序,使 volatile 的 写-读 和锁的 释放-获取 具有相同的内存语义;

(2)增强 final 的内存语义。在旧内存模型中,多次读取同一个 final 变量的值可能会不相同。为此,JSR-133 为 final 增加了两个重排序规则。在保证 final 引用不会从构造函数内逸出的情况下,final 具有了初始化安全性。


Author: NaiveKyo
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source NaiveKyo !
  TOC