Java并发-happens-before,重排序

happens-before规则

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任一后续操作
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
volatile变量规则:对一个volatile域的写,happens-before于任一后续对这个volatile域的读
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
注意:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!,happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

重排序

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

数据依赖性

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

只要重排序两个操作的执行顺序,程序的执行结果就会被改变
数据依赖性仅针对单个处理器中中性的指令序列和单个线程中的执行的操作,不同处理器之间和不同线程之前的数据依赖性不被编译器和处理器考虑

as-if-serial

不管怎么重排序(编译器和处理器为了提高并行度)(单线程)程序的执行结果不能被改变。编译器,runtime和处理器都必须遵守as-if-serial语义。
如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

重排序对多线程的影响

这里写图片描述
flag变量是个标记,用来标记变量a是否已被写入。这里假设有2个线程A,B。A首先执行write()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?
答案不一定能看到

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样操作3和操作4没有数据依赖关系,编译器和处理器页可以对这两个操作重排序。
先看下操作1和操作2重排序时,可能产生什么效果
这里写图片描述
虚箭线标识错误的读操作
操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还没有被线程A写入

操作3和操作4重排序时
这里写图片描述

当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测来执行客服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲的硬件缓存中
,当操作3的条件为真时,就把该计算结果写入变量i中。
在单线程中,对存在控制以来的操作重排序,不会改变执行结果,但在多线程中,对存在控制依赖的操作作重排序,可能会改变程序的执行结果。

猜你喜欢

转载自blog.csdn.net/qq_29842929/article/details/80789558