happens-before规则解析

happens-before规则是java memory model(JMM)最核心的概念。所以,理解happens-before规则对于理解JMM是至关重要的。

java重排序

之所以会出现happens-before规则,是因为java中存在重排序的情况。

重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重排序的一种手段。

JMM对编译器和处理器的要求是,只要你保证了程序在单线程和正确同步的多线程中执行结果的正确性,随便你对程序指令序列进行优化重排序。

所以,对应的,JMM对程序员的保证也是这样,JMM保证说程序员写的程序在单线程和正确同步的多线程条件下能得到预期的结果,但是如果多线程不正确同步时,就不保证执行结果的唯一正确性。我们来看一下简单的单线程例子:

    a = 1;
    b = 2;
    sum = a + b;

在程序里面,我们对a的赋值操作排在对b赋值的前面,但是,如果先执行对b的赋值,再执行a的赋值,对单线程程序来说是没有任何影响的,只要他们都在sum语句之前被执行,程序的结果就是正确的。JMM在这种情况下就允许对第一第二条语句进行重排序,但是不允许对第三条语句进行重排序,因为那样会改变程序的执行结果。

那么,什么是不正确同步的多线程呢?额…这个扯远了,我们还是用上面的例子来举例:

    线程A                             线程B
    a = 1;      (1)
    b = 2;
    sum = a + b;
                                    d = b;
                                    c = a;  (2)

虽然语句2在程序上来说是后于语句1被调用,但是编译器或者处理器可能会重排序成下面的结果,因为对于各自两个线程而言,单线程情况下执行结果都是没有影响的:

    线程A                             线程B
                                    c = a;  (2)
    a = 1;      (1)
    b = 2;
    sum = a + b;
                                    d = b;

那么,这时候线程B的值c就不是1了,而是a的初始值。


JMM对happens-before关系的定义

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前

  2. 两个操作之间存在happens-before关系,并不意味着java平台的具体实现必须按照happens-before关系指定的顺序来执行。如果重排序之后执行的结果,与按happens-before关系来执行的结果是一致的,那么这种排序是允许的。

我们看第一点斜体部分,其实这个是JMM用来“骗”程序员的,因为在不影响程序结果的前提下,改变程序的执行顺序程序员是感觉不到的,所以JMM就假装对程序员说第一个操作的执行顺序排在第二个操作的前面。

上面两点可以简单概括为:

  1. 会改变程序执行结果的重排序(JMM和happens-before都要求禁止)
  2. 不会改变程序执行结果的重排序(happens-before也要求禁止,但是JMM是允许的,处理器是按照JMM的规定来执行的)

happens-before规则

  1. 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
  2. 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
  3. volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
  4. Thread.start()的调用会happens-before于启动线程里面的动作。
  5. Thread中的所有动作都happens-before于其他线程从Thread.join中成功返回。
  6. 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。

这6条规则中,2~5其实是一个道理,所以我们只选择1、3、6来进行讲解。

1. 程序顺序规则

程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。

      A: a = 1;
      B: b = 2;

对于happens-before规则而言,A如果happens-before于B,它要求在发生操作B之前,操作A产生的影响都能被操作B观察到.

但是对于”不会改变程序执行结构的重排序”而言(比如说这个例子,B在执行的时刻其实根本不关心A的结果是多少),JMM不对编译器和处理器做要求,所以编译器和处理器为了性能的提高,有可能进行指令和内存重排序,也就是说B可能被排到A的前面,但是对于B而言,因为A的执行结果它不关心,所以在执行B的时刻,他可以假装看到了A操作执行后产生的影响,从这个角度来说他也符合happens-before的要求.

那么,这条规则是不是说除了保证不改变单线程的执行结果外,就没有其他作用了呢?其实不然,这里先卖个关子,等讲完其他两个规则再来解释这个会更加清楚。

2. volatile变量规则

volatile变量规则: 不管在单线程还是多线程环境下,对于一个volatile域的写,happens-before于后续任意线程对这个域的读。

    线程A                             线程B
    volatileVal = 1; (1)
                                     a = volatileVal; (2)

语句(1) (2)虽然是在不同线程中的,但是因为是volatile域,他们不能进行重排序,也就是说线程A对volatileVal变量写的值,会对后续对volatileVal变量进行读操作的线程可见。

关于volatile关键字的详细解释,有兴趣的朋友可以看我的另外一篇博文浅谈volatile

3. 传递性

传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。

看概念大家都能理解是什么意思,传递性配合上程序顺序规则,就可以实现一些其他的保证。

            线程1                      线程2
            a = 1;           (1)
            volatileVal = 2; (2)
                                        c = valatileVal; (3)
                                        d = a;           (4)
  1. 从程序顺序规则,我们有(1) happens-before (2), (3) happens-before (4).

  2. 从volatile变量规则,我们有(2) happens-before (3)

  3. 那么,根据传递性,我们就有了(1) happens-before (4)

现在我们有了(1) happens-before (4),他给我们的程序提供了什么保证呢?

从JMM对happens-before关系的定义我们可以知道,如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见。这里(1) happens-before (4),也就是说(1)的执行结果要对(4)可见,所以(4)中a的值就是1。

如果把上面的(2)和(3)换一下位置,会发生什么情况?(假设a的初始值为0)

            线程1                          线程2
            a = 1;           (1)
                                        c = valatileVal; (3)
            volatileVal = 2; (2)
                                        d = a;           (4)

同样的我们有(1) happens-before (2),(3) happens-before (4),但是我们没有(2) happens-before (3),所以也就没有了(1) happens-before (4),所以编译器和处理器有可能会将程序重排序为:

            线程1                          线程2
                                        c = valatileVal; (3)
                                        d = a;           (4)
            a = 1;           (1)
            volatileVal = 2; (2)

这时候,d的值就不是1了.d的值在这里是不确定的(可能是1,也可能是0),这就产生了线程安全问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值