在JMM中有一个很重要的概念对于咱们了解JMM有很大的帮助,那就是happens-before规则。happens-before规则很是重要,它是判断数据是否存在竞争、线程是否安全的主要依据。JSR-133S使用happens-before概念阐述了两个操做之间的内存可见性。在JMM中,若是一个操做的结果须要对另外一个操做可见,那么这两个操做则存在happens-before关系。java
那什么是happens-before呢?在JSR-133中,happens-before关系定义以下:安全
- 若是一个操做happens-before另外一个操做,那么意味着第一个操做的结果对第二个操做可见,并且第一个操做的执行顺序将排在第二个操做的前面。
- 两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。若是重排序以后的结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)
happens-before规则以下:并发
- 程序顺序规则:一个线程中的每个操做,happens-before于该线程中的任意后续操做。
- 监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile规则:对一个volatile变量的写,happens-before于任意后续对一个volatile变量的读。
- 传递性:若果A happens-before B,B happens-before C,那么A happens-before C。
- 线程启动规则:Thread对象的start()方法,happens-before于这个线程的任意后续操做。
- 线程终止规则:线程中的任意操做,happens-before于该线程的终止监测。咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 线程中断操做:对线程interrupt()方法的调用,happens-before于被中断线程的代码检测到中断事件的发生,能够经过Thread.interrupted()方法检测到线程是否有中断发生。
- 对象终结规则:一个对象的初始化完成,happens-before于这个对象的finalize()方法的开始。
以上8条happens-before规则都比较简单,这里LZ只分析第3条volatile变量规则,分析以下:app
从上图中,咱们看到存在4条happens-before关系,它们分别以下:this
- 1 happens-before 2 和 3 happens-before 4 是有由程序顺序性规则产生的。
- 2 happens-before 3 是由volatile规则产生的。上面提到过,一个volatile变量的读,总能看到以前对这个volatile变量的写入。
- 1 happens-before 4 是由传递性规则产生的。
读到这里,可能不少童鞋会把happens-before理解为“时间上的前后顺序”,在这里LZ特别强调happens-hefore不能理解为“时间上的前后顺序”,下面LZ用一段代码解释写happens-before和“时间上的前后顺序”的不一样,代码以下:线程
public class VolatileTest4 { private int a = 0; public int getA() { return a; } public void setA(int a) { this.a = a; } }
上面代码就是一组简单的setter/getter方法,如今假设如今有两个线程A和B,线程A先(这里指时间上的先执行)执行setA(10),而后线程B访问同一个对象的getA()方法,那么此时线程B收到的返回值是对少呢?code
答案是:不肯定对象
咱们来一次分析下happens-before的各项原则:blog
- 这里两个方法分别是在两个线程中被调用,不在一个线程中,这里程序顺序性就不适用了
- 代码中没有同步快,全部监视器规则也不适用
- 代码中变量a是一个普通变量,因此volatile规则也不适用
- 后面的线程启动、中断、终止和对象的终结和这里彻底没有关系,所以这些规则也是不适用的
- 没有一条happens-before适用,所以传递性规则也不适用
在这里,虽然线程A在时间上先于线程B执行,可是因为代码彻底不适用happens-before规则,所以咱们没法肯定先B收到的值时多少。也就是说上面代码是线程不安全的。排序
对于上面代码,那咱们如何修复线程不安全这个问题呢?这里,咱们只要知足happens-before规则中二、3的任意一种规则就能够了。即要么把setter/getter方法定义为synchronized方法,要么在变量a上加volatile修饰符。
经过上面的例子,咱们能够得出结论:一个操做“时间上的先发生”不表明这个操做会happens-before其它操做。那一个操做happens-before其它操做,是否就表示这个操做是“时间上先发生”的呢?答案也是否认的,咱们来看看下面一个示例:
int i = 1; int m = 2;
上面两个赋值操做在同一个线程中,根据程序顺序性规则,“int i = 1;"这个操做happens-before ”int m = 2;“这个操做,可是”int m = 2;“这个操做彻底有可能被处理器先执行,这并不影响happens-before原则的正确性。由于这种重排序在JMM中是容许的。
最后咱们得出的结论是:时间前后顺序与happens-before原则之间基本没有太大的关系,因此咱们在衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以happens-before原则为准。