Java 并发(三),java 程序设计教程雍俊海第三版答案
happens-before 是一种关系,在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系,注意,这里的两个操作既可以是不同线程,也可以是同一个线程
那 happens-before 有什么规则了
程序顺序规则:一个线程中的每个操作,该线程中的任意后续动作都必须可以看到前面操作的结果,所以 happens-before 于该线程的任意后续动作
监视器锁规则:当一个锁解锁后,后面的加锁动作都要可以看到解锁动作,所以 happens-before 于随后对这个锁的加锁
volatile 变量规则:volatile 实现了变量的线程可见性,所以对这个变量的操作都要被后续可见,所以 happens-before 于任意后续对这个 volatile 域的读
传递性:如果 B 可见 A,即 A 可以 happens-before 于 B,如果此时,C 又可见 B,即 B 可以 happens-before 于 C,那么对于 A 和 C,A 可以 happens-before 于 C
其实 happens-before 只是一个规则,抽象了 JMM 提供的内存可见性而已,也就是不用去认识透彻前面提到过的各种重排序,而 happens-before 的实现其实也就是 JMM 禁止了各种重排序
前面我们已经简单了解过重排序
重排序是指:编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,关键在于是为了优化性能,而且要注意,每个线程都会发生重排序,因为处理器每次执行都只是执行一个线程
数据依赖性
数据依赖性是指:**有两个操作访问同一个变量,并且这些操作至少有一个为写操作,那么此时这两个
操作就存在数据依赖性**,也因此数据依赖性根据两个操作的顺序会分为三种
写后读:写入一个变量之后,再进行读取
读后写:读一个变量之后,再进行写入,注意这里是写入而不是修改,比如,a = b;b = 1 就是一个读后写
写后写:写一个变量之后,再重新进行写入
上面三种数据依赖性,只要发生操作的重排序,程序的执行结果都会被改变,而前面已经提到过,编译器和处理器是会对操作进行重排序的,所以为了防止执行结果发生改变,编译器和处理器要辨别出操作是否存在数据依赖性,如果存在数据依赖性是不会进行重排序的,但这种自动禁止重排序操作仅仅出现在单线程和单处理器,也就是仅仅只会考虑单线程和单处理器的数据依赖性,对于不同线程和不同处理器之间的数据依赖性是不会被考虑的
as-if-serial 语义
as-if-serial 语义是指:不管怎么进行重排序,程序的执行结果都不能改变,当然这也只是针对单线程,也就是单线程的执行结果都不能改变,不保证多线程是否发生了改变
举个栗子
double pi = 3.14; //A 操作
double r = 1; //B 操作
double area = pi * r * r; //C 操作
在上面的三个操作,产生数据依赖性的有 A 与 C、B 与 C,而且产生的都是写后写数据依赖性,那么 A 与 B 是没有数据依赖性的,这两个操作发生重排序是不会违反 as-if-serial 语义,所以这两个操作允许发生重排序,但是 C 操作就不可以随便发生重排序了,必须要满足 A-happensbefore-C 与 B-happensbefore-C
总的来说,as-if-serial 语义是将单线程程序保护了起来,不用去考虑重排序导致的问题,让开发者可以认为程序就是按顺序执行的,重排序不会干扰
as-if-serial 也允许对存在控制依赖的操作进行重排序
控制依赖就是指:逻辑判断操作,即 if 那些判断语句,那些判断语句也是一个操作,具体来说就是,允许先执行 if 里面的代码块,然后再判断 if 的条件是否为 True 或者 False
因为控制依赖会影响指令序列执行的并行度,本可以执行多个命令的,偏偏要先去执行判断命令,等判断完再去执行其他命令,这会降低了指令序列的并行度,所以干脆就一起并行执行,判断条件后再考虑结果是否保留即可,即允许发生重排序
重排序对多线程的影响
重排序是针对单线程进行的,单线程发生重排序是没有任何问题的,因为有着 as-if-serial 语义的保证,但是多线程各自线程发生重排序,组合起来就会产生多线程的语义错误,把程序的执行结果给改变
举个栗子
假如 A 线程修改了一个 flag 变量,而 B 线程去获取这个 flag 变量,那么由于 A 的重排序,将修改 flag 变量的操作提前或者延后了,B 线程获取的 flag 变量可能为修改前的,也可能为修改后的
程序一致性是用来形容多线程同步执行的,规则如下
一个线程中的所有操作必须按照程序的顺序来执行
所有线程都只能看到一个单一的操作执行顺序,不管是同步还是不同步,每个操作都必须是原子执行且立刻对所有线程可见
举个栗子
有一个线程 A,拥有三个操作,A1、A2、A3;另外一个线程 B,也有三个操作,B1、B2、B3
那么在同步的时候,这 2 个线程共 6 个操作的执行顺序如下所示(假设 A 线程先执行)
可以看见,每个线程的三个操作都必须是按顺序执行的
评论