JMM之happens-before

在JMM中有一个很重要的概念对于咱们了解JMM有很大的帮助,那就是happens-before规则。happens-before规则很是重要,它是判断数据是否存在竞争、线程是否安全的主要依据。JSR-133S使用happens-before概念阐述了两个操做之间的内存可见性。在JMM中,若是一个操做的结果须要对另外一个操做可见,那么这两个操做则存在happens-before关系。java

那什么是happens-before呢?在JSR-133中,happens-before关系定义以下:安全

  1. 若是一个操做happens-before另外一个操做,那么意味着第一个操做的结果对第二个操做可见,并且第一个操做的执行顺序将排在第二个操做的前面。
  2. 两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。若是重排序以后的结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)

happens-before规则以下:并发

  1. 程序顺序规则:一个线程中的每个操做,happens-before于该线程中的任意后续操做。
  2. 监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  3. volatile规则:对一个volatile变量的写,happens-before于任意后续对一个volatile变量的读。
  4. 传递性:若果A happens-before B,B happens-before C,那么A happens-before C。
  5. 线程启动规则:Thread对象的start()方法,happens-before于这个线程的任意后续操做。
  6. 线程终止规则:线程中的任意操做,happens-before于该线程的终止监测。咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  7. 线程中断操做:对线程interrupt()方法的调用,happens-before于被中断线程的代码检测到中断事件的发生,能够经过Thread.interrupted()方法检测到线程是否有中断发生。
  8. 对象终结规则:一个对象的初始化完成,happens-before于这个对象的finalize()方法的开始。

以上8条happens-before规则都比较简单,这里LZ只分析第3条volatile变量规则,分析以下:app

happens-before之volatile规则

从上图中,咱们看到存在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

  1. 这里两个方法分别是在两个线程中被调用,不在一个线程中,这里程序顺序性就不适用了
  2. 代码中没有同步快,全部监视器规则也不适用
  3. 代码中变量a是一个普通变量,因此volatile规则也不适用
  4. 后面的线程启动、中断、终止和对象的终结和这里彻底没有关系,所以这些规则也是不适用的
  5. 没有一条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原则为准。

相关文章
相关标签/搜索