一个操做执行的结果须要对另外一个操做可见,那么这两个操做之间必须存在happens-before关系程序员
happen-before原则是JMM中很是重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。安全
happens-before原则定义:多线程
1. 若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前。
2. 两个操做之间存在happens-before关系,并不意味着必定要按照happens-before原则制定的顺序来执行。若是重排序以后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。并发
下面是happens-before规则:app
1)一个线程中的每一个操做,happens- before 于该线程中的任意后续操做。优化
2)监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。线程
3)volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。code
4)传递性:若是A happens- before B,且B happens- before C,那么A happens- before C。对象
(happens-before如下部分转自【死磕Java并发】—–Java内存模型之happens-before)blog
咱们来详细看看上面每条规则(摘自《深刻理解Java虚拟机第12章》):
程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,由于虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,可是并不会影响程序的执行结果,因此程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下没法保证正确性。
锁定规则:这个规则比较好理解,不管是在单线程环境仍是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操做后面才能进行lock操做。
volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是若是一个线程先去写一个volatile变量,而后一个线程去读这个变量,那么这个写操做必定是happens-before读操做的。
传递规则:提现了happens-before原则具备传递性,即A happens-before B , B happens-before C,那么A happens-before C
线程启动规则:假定线程A在执行过程当中,经过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
线程终结规则:假定线程A在执行的过程当中,经过制定ThreadB.join()等待线程B终止,那么线程B在终止以前对共享变量的修改在线程A等待返回后可见。
上面八条是原生Java知足Happens-before关系的规则,可是咱们能够对他们进行推导出其余知足happens-before的规则:
这里再说一遍happens-before的概念:若是两个操做不存在上述(前面8条 + 后面6条)任一一个happens-before规则,那么这两个操做就没有顺序的保障,JVM能够对这两个操做进行重排序。若是操做A happens-before操做B,那么操做A在内存上所作的操做对操做B都是可见的。
JMM把happens- before要求禁止的重排序分为了下面两类:
1)会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
2)不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM容许这种重排序,如as-if-serial)。
只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。
好比,若是编译器通过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁能够被消除。
再好比,若是编译器通过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器能够把这个volatile变量看成一个普通变量来对待。
这些优化既不会改变程序的执行结果,又能提升程序的执行效率。
数据依赖性
若是两个操做访问同一个变量,且这两个操做中有一个为写操做,此时这两个操做之间就存在数据依赖性
只要重排序两个操做的执行顺序,程序的执行结果将会被改变
编译器和处理器在重排序时,会遵照数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操做的执行顺序
写后读 a = 1;b = a; 写一个变量以后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量以后,再写这个变量。
读后写 a = b;b = 1; 读一个变量以后,再写这个变量。
只针对单线程
as-if-serial语义
无论怎么重排序(编译器和处理器为了提升并行度),程序的执行结果不能被改变(只针对单线程)
编译器和处理器遵照数据依赖性缘由:为了遵照as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操做作重排序,由于这种重排序会改变执行结果
遵照as-if-serial语义,单线程程序的程序员建立了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担忧重排序会干扰他们,也无需担忧内存可见性问题
// 举例:可能A-->B--C 也可能B-->A-->C double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C