重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
但是不能随时随地的重排序,我们可以看一个例子看待这个问题。
上面三种情况,只要重排序两个操作的执行顺序,那么程序的结果就会被改变。
所以 重排序需要遵守一些规则。
a、编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序
b、单线程下,不能改变数据的执行结果
一、数据依赖性
在上面的情况中,我们可以看到,有两个操作去访问同一个变量,并且两个操作中有一个为写操作,此时,这两个操作之间就存在数据依赖性。
数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑
二、as-if-serial语义
as-if-serial的意思是:不管怎么重排序,单线程程序的结果不能被改变。编译器、处理器都必须遵守as-if-serial语义
在举一个例子
上面三个操作的数据依赖性如下图所示
可以看到,A和B并不存在数据依赖性,所程序的执行顺序就有两种可能,A-B-C或B-A-C,并且执行结果都为3.14
在这里,as-if-serial语义吧单线程程序保护了起来,使得程序员无需担心重排序会干扰他们。
三、程序顺序规则
根据happens-before的程序顺序规则,在上面的例子中存在3个happens-before关系:
①A happens-before B
②B happens-before C
③A happens-before C
这里A happens-before B,但实际执行时B却可以排在A前面(上面第二种情况),如果A happens-before B,Java内存模型并不要求A一定在B之前执行。Java内存模型仅仅要求前一个操作的执行结果对后一个操作可见,且前一个操作按顺序排在第二个操作之前。在这里操作A的执行结果并不需要对操作B可见,所以在这种情况下,Java内存模型认为这种重排序并不非法。
四、重排序对多线程的影响
直接看例子
在这程序中,如果有两个线程A和B,A首先执行write方法,随后B线程执行multiply方法。线程B在执行操作时,能否读到线程A设置的变量a的值呢。
答案是不一定能看到
由于给 a赋值和给flag赋值是没有数据依赖关系的,编译器和处理器可以对这两个操作进行重排序,在这个时候,如果发生了重排序,会有什么样的事情发生呢
可以看到,线程A首先将flag写入true,而后线程B读这个flag,此时变量a的值还没有被线程A写入,取到的a值其实是0,在这里多线程程序语义被重排序破坏了。
在单线程程序中,重排序不会改变执行结果,但在多线程程序中,会破坏多线程程序的语义,可能会改变程序的执行结果。