Java内存模型二(重排序)

重排序:指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

1、数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性

数据依赖性分为下列3种:

名 称 代码示例 说 明
写后读 a = 1; b = a; 写一个变量之后,再读这个变量
写后写 a = 1; a = 2; 写一个变量之后,在写这个变量
读后写 a = b; b = 1; 读一个变量之后,再写这个变量

以上3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。

编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序

注意:数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

2、as-if-serial语义

as-if-serial 语义的意思是:不管怎样重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

as-if-serial 语义使单线程程序不会发生因重排序而导致的线程安全问题,也无需担心内存可见性问题。

3、程序顺序规则

如果:

1
2
3
4
// 求圆的面积
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; //C
  1. A happens-before B
  2. B happens-before C
  3. A happens-before C

这里,A happens-before C 是由1、2传递性推导出来的。A 并不一定要在 B 之前执行,这里仅表示 A 的结果要对 B 可见。

如果 A 和 B 发生重排序,并不影响执行结果,JMM会认为这种重排序并不非法,JMM允许这种重排序。即:在不改变程序执行结果的前提下,尽可能提高并行度,来提升性能。

4、重排序对多线程的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 多线程并发执行此代码
class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
a = 1; // 1
flag = true; // 2
}

public void reader() {
if (flag) { // 3
int i = a * a; // 4
...
}
}
}

从程序中看出,操作 1 和 2 没有数据依赖关系,操作 3 和 4 也没有数据依赖关系,因此重排序会影响多线程执行此程序的结果。

在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。


Java内存模型二(重排序)
https://cuilan.github.io/2019/05/27/并发编程/java内存模型二/
作者
zhang.yan
发布于
2019年5月27日
许可协议