首先跟大家确定一个大前提是内存模型是与多线程息息有关的。这篇文章主要讨论的是多线程与内存的关系。
实际上,内存模型对单线程而言,它只保证程序在单线程执行的情况下,程序能够得到正确的结果。
一、内存模型是什么
内存模型是对内存进行读写访问过程的抽象。也可以这样说,内存模型定义正确的内存读写行为(单线程)。
二、重排序是什么
重排序是重新调整语句或指令的顺序去提高程序的执行性能。
编译器重排序: 不改变在单线程中程序执行结果时,可以重新安排语句的执行顺序。
处理器重排序: 由于处理器使用缓存、缓冲区等技术,使得对内存的加载和存储看上去可能在乱序执行。
三、as-if-serial语义是什么
无论怎么重排序,程序在单线程下的执行结果不能被改变。
四、几种内存模型的比较
顺序一致性内存模型是一个理论的内存模型,处理器内存模型和Java内存模型都是在此基础上进行不同程度的弱化,
目的是为了提高程序执行的性能。
4.1、顺序一致性内存模型
4.1.1、什么是顺序一致性内存模型
通俗语义是多线程的程序执行应该和多个线程在一个处理器核上交错执行的效果一致。
4.1.2、顺序一致性内存模型的特点
禁止编译器优化:按(单线程)程序的顺序依次访问内存。
禁止处理器优化:每个写的操作是立即可见的通过内存。
4.1.3、内存模型与多线程之间的关系
图 4-1-1
通过一个例子来说明顺序一致性内存模型
图 4-1-2
这个会有几种的执行路径和可能的结果
第一种
第二种
第三种
第四种(不可能出现的结果)
图 4-1-3
从图4-1-2的示例中,我们可以很快得出几种执行路径和结果,在图4-1-3中清晰展示可能的执行路径和结果。那为什么第四种情况在顺序一致性内存模型不可能出现了,
是由顺序一致性内存模型的特点所决定的,在顺序一致性内存模型中是禁止处理器重排序的,导致第四种的的执行结果不能出现 。什么请求下会出现第四种情况了,就是接
下来要讲处理器内存模型。
4.2、处理器内存模型
处理器内存模型是在顺序一致性内存模型基础上放松其中一个或多个操作顺序(写读、写写、读读和读写)而产生的一种内存模型。
注意:这里的放松是指允许写读、写写、读读和读写其中一种或多种操作可以重排序。
这里以TSO的处理器内存模型来讲解
TSO内存模型是在顺序一致性内存模型基础放松对写读操作顺序(重排序)而产生的一种内存模型。
4.2.1、内存模型与多线程关系
由于现代处理器都使用写缓冲区,因此现代处理器都允许都写读操作进行重排序。
图 4-2-1
示例:
图 4-2-2
TSO除了以上顺序一致性内存模型产生执行路径和结果之外,还有一种tso特有的执行路径和结果
该图中左边图是引用于Sequential Consistency &TSO文章中图和右图引用于深入理解 Java 内存模型(一)——基础
该图中左边图是引用于Sequential Consistency &TSO文章中图和右图引用于深入理解 Java 内存模型(一)——基础
图4-2-3
图4-2-2中示例,可能会出现的执行结果在图4-2-3中进行展示。从图4-2-3可以看出这是由于处理器中执行顺序与内存访问顺序不一致而出现的结果,这是由于处理器内存系统导致的重排序。
那下面要讲的Java内存模型会扮演什么的角色了。
问题:
1、编译器的重排序与处理器中都写读可以进行重排序有什么区别。
2、如果编译器进行读读重排序,但是TSO的内存模型是不允许进行读读重排序的,这两者就会产生矛盾。
4.3、Java内存模型
它描述一组规则或是规范,这个定义了一个线程对共享变量的写入何时对另一个线程可见。
它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化
4.3.1、内存模型与多线程的关系
从图4-3-1中我们可以明显看出Java
该图来引用于深入理解Java内存模型文章中图
图 4-3-1 该图来引用于深入理解Java内存模型文章中图
4.3.2、正确同步的程序、未正确同步或是未同步的程序
未同步的多线程程序
编译器
class Demo1 {
int x = 0;
int y = 0;
public void write() {
y = 1;
int r1 = x;
}
public void read () {
x = 2;
int r2 = y;
}
}
可能的执行路径和结果:
图 4-3-2
处理器
如上文提到处理器内存模型中示例,产生的执行路径和结果
正确同步的多线程程序
编译器:
class Demo1 {
volatile int x = 0;
volatile int y = 0;
public void write() {
y = 1;
int r1 = x;
}
public void read () {
x = 2;
int r2 = y;
}
}
执行路径和结果
图 4-3-3
读者可以自行画其它的执行路径和结果。
Sparc-TSO处理器:Sparc-TSO处理器使用的内存模型是TSO(上文所讲的)
示例
图4-3-4
通过在定义x和y变量时,加一个volatile修饰符。在最终执行的指令序列中会在S1和L1,S2和L2之间分别添加一个StoreLoadBarrier内存屏障。最终达到下图效果。
图 4-3-5
右边线程与左边的处理器在这里是一致的
这使得指令在执行顺序与在内存中的执行顺序一致
处理器中执行顺序:A1:S1 => A2 => A3:L1
内存中执行顺序: A1:S1 => A3:L1
java内存模型控制线程对内存的访问
结论:正确同步Java内存模型的多线程跟顺序一致性的内存模型的执行路径是一致的。
正确同步多线程+Java内存模型 == 顺序一致性的内存模型。
4.3.3、Java的内存模型对程序员是透明的
Java的内存模型是对程序员是透明的,那Java内存模型是已什么方式呈现给程序员,
Java内存模型是已happens-before方式呈现给程序员的。
4.3.4、happens-before规则
概念
后一个语句执行需要依赖前一个语句的执行结果,且前一个语句按顺序排在后一个语句的前面。
但并不意味后一个语句一定在前一个语句之后执行。
int a = 1;
int b = 2;
int c = a + b + 1;
执行路径
图 4-3-6
程序如果能优化,编译器和处理器会尽可能的进行优化(即是提高性能)。
几个规则
程序顺序规则、监视器锁的规则、volatile变量规则、传递性等,具体含义可以参照SR-133: Java Memory Model and Thread Specification、深入理解 Java 内存模型的系列文章等文章。
4.3.5、解决什么问题
它保证内存一致性,对程序员是透明的,它是通过解决重排序问题来保证的
它能禁止某种特定类型的编译器重排序
它能禁止某种特定类型的处理器重排序
通过内存屏障来禁止处理器重排序。在生成最终指令的序列过程中插入内存屏障(在使用同步原语(lock,volatile等)适当的位置插入)。
并不是对所有的处理器重排序都禁止,它只是禁止某种特定类型的重排序。
4.4、Java内存模型、处理器内存模型和顺序一致性内存模型关系
顺序一致性内存模型是一个理论的内存模型。处理器内存模型是参照顺序一致性内存模型进行弱化的实现。
参考文档