重排序指的是编译器和处理器为了优化程序性能而对指令序列进行重排序的手段
数据依赖性
如果两个操作访问同一个变量,且这两个操作有一个是写操作,此时这两个操作间存在数据依赖。
名称 | 代码示例 | 说明 |
写后读 | a=1; b=a; | 写一个变量后,在读这个变量 |
写后写 | a=1; a=2 | 写一个变量后,在写这个变量 |
读后写 | a=b; a=1; | 读一个变量后,在写这个变量 |
编译器和处理器可能对操作做重排序,编译器和处理器在重排序时候,会遵循数据依赖性,编译器和处理器不会对存在数据依赖性的两个操作执行顺序。
但是,数据依赖性只是针对单个处理器中执行的指令序列和单线程中执行的操作,不同处理器和不同线程间的数据依赖性不被编译器和处理器考虑。
as-if-serial 语义
as-if-serial语义的含义是不管怎么重排序,单线程程序的执行结果不能变,(编译器、runtime和处理器都必须遵循as-if-serial)。
为了遵守as-if-serial原则,编译器和处理器不能对存在数据依赖性的操作重排序。但是如果操作间不存在数据依赖性,就可以重排序。
double pi = 3.14;//操作A
double r = 3.00;//操作B
double area = pi*r*r;//操作C
操作A和操作C存在数据依赖性,操作B和操作C存在数据依赖性,都不能重排序,但是操作A和操作B的重排序不会影响程序执行结果。
as-if-serial语义的结果:单线程无需担心内存可见性问题,产生一种幻觉,单线程的执行是按照程序顺序执行的。
程序顺序规则
根据happen-before原则,上面计算圆面积的代码存在3个happen-before原则
1. A happen before B;
2. B happen before C;
3. A happend before C;
这里A happen before B ,不要求A先于B执行,仅要求A执行结果对B是可见的并顺序上排在B前面。
操作A和操作B不存在数据依赖性,重排序两个操作和按照happen-before结果一致,在这种情况下,jmm不会认为重排序不非法。
重排序对多线程的影响
class RecorderExample{
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
}
}
}
线程A : 执行 writer()
线程B : 执行reader()
多线程环境下,线程B不一定能看到线程A对操作1的变量a的写入
一种情况,线程A 中 操作1 和操作2 发生重排序,线程B读取到flag为true,执行操作4 时候,a还没有赋值
结论:多线程环境下, 重排序会改变程序的执行结果
顺序一致性
数据竞争和顺序一致性
当程序未正确同步时候,就可能存在数据竞争,java内存模型规范对数据竞争的定义如下:
在一个线程中写入一个变量。
在另一个线程中读同一个变量,而且写和读没有通过同步来排序
jmm 对正确同步的多线程程序的内存一致性做了如下保证:
如果程序是正确同步的,程序的执行将具有顺序一致性,即程序的执行结果和在顺序一致性内存模型中执行的结果一致。这里的同步包括对常用(synchronized,volatlie,final)正确使用。
顺序一致性内存模型
两个特性:
1> 一个线程中的所有操作必须按照程序的顺序执行
2> 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序,在顺序一致性模型中,每个操作都必须原子执行并且立刻对所有线程可见。
顺序一致模型对程序员的视图
顺序一致模型,有一个单一的全局内存,当多个线程并行执行时候,图中的开关操作将所有线程的所有内存读写串化。
举个例子:两个线程通过锁同步,线程A释放锁,线程B获得锁
线程A ; 程序执行顺序 A1>A2>A3
线程B; 程序执行顺序 B1>B2>B3
在顺序一致性模型中执行顺序: A1>A2>A3>B1>B2>B3
如果两个线程没有正确同步,整体的执行顺序无序,并且不具有内存可见性.
结论:1.同步程序执行结果和在顺序一致性模型执行结果相同,单个线程在临界区重排序的执行,对另一个线程是不可见的,这种重排序,既提高了执行效率又没有改变程序的执行结果
2. jmm不保证未同步的执行结果和在顺序一致性模型执行结果一致,如果想保证执行结果的一致性,jmm需要禁止大量的编译器和处理器优化,影响程序执行性能,而且在顺序一致性模型中,未同步的程序,整体是无序的,这样保证两者的执行结果一致没有意义。
3. 未同步程序在jmm模型和顺序一致模型执行的差异:
1> 顺序一致模型保证单线程的操作是按照 顺序执行的,但是jmm会对程序执行性能优化,进行重排序
2> 顺序一致模型保证所有线程都只能看到一致的操作执行顺序,jmm不保证所有线程看到一致操作执行顺序
3> jmm不保证64位的long型和double变量写操作具有原子性,而顺序一致模型保证所有读写具有原子性。