happens-before规则

概念

Java语言中有一个“先行发生”(happens-before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的先后发生基本没有太大关系。这个原则特别重要,它是判断数据是否存在竞争、线程是否安全的主要依据。

举例:
假设存在三个线程A、B、C,他们分别对应如下三个操作:
A线程操作:i=1;
B线程操作:j=i;
C线程操作:i=2;
假设线程A中的操作“i=1”happens-before线程B中的操作“j=i”,那么就可以保证在线程B的操作执行后,变量j的值一定为1,即线程B观察到了线程A中操作“i=1”所产生的影响;现在,我们依然保持线程A和线程B之间的happens-before关系,同时线程C出现在了线程A和线程B的操作之间,但是C与B并没有happens-before关系,那么j的值就不确定了,线程C对变量i的影响可能会被线程B观察到,也可能不会,这时线程B就存在读取到不是最新数据的风险,不具备线程安全性。

与程序员密切相关的happens-before规则

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。意思就是说,在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happens-before(时间上)后执行的操作。
监视器锁规则:对一个监视器锁的解锁,happens-before于随后对这个监视器锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

时间上执行的先后顺序“与”happens-before“之间的区别?
1.如果线程操作A在时间上先于线程操作B,是否意味着操作A happens-before 操作B?
举例:

private int value = 0;  

public int getValue(){  
    return value;  
}  
public void setValue(int value){  
    this.value = value;  
}  

假设存在线程A和线程B,线程A先(时间上的先)调用了setValue(3)操作,然后(时间上的后)线程B调用了同一对象的getValue()方法,那么线程B得到的返回值一定是3吗?这显然是不能保证的,因为线程A和线程B并不满足happens-before关系,也就是说线程A所做的操作并不一定能被线程B知道,其实还是线程可见性问题,因为每个线程都有自己独立的工作内存,线程A做的操作并不能立马刷新主内存,从而线程B就无法获取最新的值。
解决方法:可以将setValue(int)方法和getValue()方法均定义为synchronized方法,也可以把value定义为volatile变量(value的修改并不依赖value的原值,符合volatile的使用场景),分别对应happens-before规则的第2和第3条。注意,只将setValue(int)方法和getvalue()方法中的一个定义为synchronized方法是不行的,必须对同一个变量的所有读写同步,才能保证不读取到陈旧的数据,仅仅同步读或写是不够的 。

2.操作A happens-before 操作B,是否意味着操作A在时间上先与操作B发生?
举例:

x = 1
y = 2;

假设同一个线程执行上面两个操作:操作A:x=1和操作B:y=2。根据happens-before规则的第1条,操作A happens-before 操作B,但是由于编译器的指令重排序(Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整)等原因,操作A在时间上有可能后于操作B被处理器执行,但这并不影响happens-before原则的正确性。

总结:最后,一个操作和另一个操作必定存在某个顺序,要么一个操作或者是先于或者是后于另一个操作,或者与两个操作同时发生。同时发生是完全可能存在的,特别是在多CPU的情况下。而两个操作之间却可能没有happens-before关系,也就是说有可能发生这样的情况,操作A不happens-before操作B,操作B也不happens-before操作A,用数学上的术语happens-before关系是个偏序关系。两个存在happen-before关系的操作不可能同时发生,一个操作A happens-before 操作B,它们必定在时间上是完全错开的,这实际上也是同步的语义之一(独占访问)。

猜你喜欢

转载自blog.csdn.net/fu123123fu/article/details/79903298