处理器内存模型
内存模型划分
放松程序中写-读操作的顺序,由此产生了Total Store Ordering内存模型(简称为TSO)。
在上面的基础上,继续放松程序中写-写操作的顺序,由此产生了Partial Store Order内存模型(简称为PSO)。
在前面两条的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了RelaxedMemory Order内存模型(简称为RMO)和PowerPC内存模型。
这里处理器对读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的。
从表3-12中可以看到,所有处理器内存模型都允许写-读重排序,原因在第1章已经说明过:它们都使用了写缓存区。写缓存区可能导致写-读操作重排序。同时,我们可以看到这些处理器内存模型都允许更早读到当前处理器的写,原因同样是因为写缓存区。由于写缓存区仅对当前处理器可见,这个特性导致当前处理器可以比其他处理器先看到临时保存在自己写缓存区中的写。表3-12中的各种处理器内存模型,从上到下,模型由强变弱。越是追求性能的处理器,内存模型设计得会越弱。因为这些处理器希望内存模型对它们的束缚越少越好,这样它们就可以做尽可能多的优化来提高性能。
由于常见的处理器内存模型比JMM要弱,Java编译器在生成字节码时,会在执行指令序列的适当 位置插入内存屏障来限制处理器的重排序。同时,由于各种处理器内存模型的强弱不同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM在不同的处理器中需要插入的内存屏障的数量和种类也不相同。
JMM屏蔽了不同处理器内存模型的差异,它在不同的处理器平台之上为Java程序员呈现了一个一致的内存模型。
各种内存模型之间的关系
JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型、处理器内存模型和顺序一致性内存模型的强弱对比示意图,如图3-49所示。
从图中可以看出:常见的4种处理器内存模型比常用的3种语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。同处理器内存模型一样,越是追求执行性能的语言,内存模型设计得会越弱。
JMM的内存可见性保证
-
单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
-
正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
-
未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。
最小安全性保障与64位数据的非原子性写并不矛盾。它们是两个不同的概念,它们“发生”的时间点也不同。
最小安全性“发生”在对象被任意线程使用之前。64位数据的非原子性写“发生”在对象被多个线程使用的过程中(写共享变量)。
64位数据的非原子性写“发生”在对象被多个线程使用的过程中(写共享变量)。当发生问题时(处理器B看到仅仅被处理器A“写了一半”的无效值),这里虽然处理器B读取到一个被写了一半的无效值,但这个值仍然是处理器A写入的,只不过是处理器A还没有写完而已。
最小安全性保证线程读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。但最小安全性并不保证线程读取到的值,一定是某个线程写完后的值。最小安全性保证线程读取到的值不会无中生有的冒出来,但并不保证线程读取到的值一定是正确的。