as-if-serial语义和happens-before原则

1、重排序

为了提高程序的执行性能,编译器(如JVM在编译Java代码)和处理器(如CPU在执行JVM字节码)常常会对指令做成重排序。

在Java中重排序有以下的几种:

指令并行重排序和内存系统重排序属于处理器重排序。

2、as-if-serial语义

重排序是需要遵守一定的规章制度才可对指令做重排序,这个规章制度就是as-if-serial语义(即就是在单线程执行的情况下,不管怎么重排序都不会导致程序的执行结果发生改变)。

如何判断程序的执行结果是否发生呢?可以通过数据依赖关系(即就是如果两个操作访问同一个变量,且这两个操作中有一个为写操作,另外一个操作可以是写操作可以是读操作,此时这两个操作之间就存在数据依赖性)来判断,下面列举几个存在数据依赖关系的案例:

#--------------写后读------------ 
a=2; 
b=a; 
#--------------写后写------------- 
a=2; 
a=3; 
#--------------读后写------------- 
a=b; 
b=1;

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。编译器和处理器不会对存在数据依赖关系的操作做重排序。

3、happens-before原则

在单线程下编译器和处理器遵循了as-if-serial语义是不会出现问题,但是在多线程下程序会会出现内存的可见性问题,为此需要禁止指令的重排序。禁止指令重排序针对编译器和处理器有不同方式:

(1)针对编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序。

(2)针对处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,来禁止特定类型的处理器重排序。下面展示了常见的内存屏障

处理器重排序是利用内存屏障来实现的,那么指令添加内存屏障需要遵循happens-before原则。

Java语言里面happens-before本质上是一种可见性,如A Happens-Before B意味着A事件对B事件来说是可见的,并且无论A和B是否发生在同一个线程里。

happens-before六大原则:

(1)原则一:程序顺序规则

一个线程中的每个操作,happens-before于该线程中的任意后续操作

(2)原则二:监视器锁规则

一个unlock操作之前对某个锁的lock操作必须发生在该unlock提作之前。

(3)原则三:volatile变量规则

对一个volatile变量的写操作必须发生在该变量的读操作之前

(4)原则四:传递性规则

如果A happens-before B,且B happens-before C,那么A happens-before C

(5)原则五:线程启动规则

如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。(线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作)

(6)原则六:线程结束规则

如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作 happens-before与 ThreadB.join()操作成功返回后的线程A操作。

7