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操作。