原创

Java内存模型的先行发生原则(Happen-Before)

1.先行发生原则

如果 Java 内存模型中所有的有序性都要依靠 volatile 和 synchronized 来实现,那是不是非常繁琐。Java 语言中有一个“先行发生原则”,是判断数据是否存在竞争、线程是否安全的主要依据。

2.什么是先行发生原则

先行发生原则是 Java 内存模型中定义的两个操作之间的偏序关系。比如说操作 A 先行发生于操作 B,那么在 B 操作发生之前,A 操作产生的“影响”都会被操作 B 感知到。这里的影响是指修改了内存中的共享变量、发送了消息、调用了方法等。个人觉得更直白一些就是有可能对操作 B 的结果有影响的都会被 B 感知到,对 B 操作的结果没有影响的是否感知到没有太大关系。

3.Java 内存模型自带先行发生原则有哪些

程序次序原则

在一个线程内部,按照代码的顺序,书写在前面的先行发生与后边的。或者更准确的说是在控制流顺序前面的先行发生与控制流后面的,而不是代码顺序,因为会有分支、跳转、循环等。

管程锁定规则

一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。这里必须注意的是对同一个锁,后面是指时间上的后面

volatile 变量规则

对一个 volatile 变量的写操作先行发生与后面对这个变量的读操作,这里的后面是指时间上的先后顺序

线程启动规则

Thread 对象的 start()方法先行发生与该线程的每个动作。当然如果你错误的使用了线程,创建线程后没有执行 start 方法,而是执行 run 方法,那此句话是不成立的,但是如果这样其实也不是线程了

线程终止规则

线程中的所有操作都先行发生与对此线程的终止检测,可以通过 Thread.join()和 Thread.isAlive()的返回值等手段检测线程是否已经终止执行

线程中断规则

对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 Thread.interrupted()方法检测到是否有中断发生。

对象终结规则

一个对象的初始化完成先行发生于他的 finalize 方法的执行,也就是初始化方法先行发生于 finalize 方法

传递性

如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

4.举例说明先行发生原则:

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

如果有两个线程 A 和 B,A 先调用 setValue 方法,然后 B 调用 getValue 方法,那么 B 线程执行方法返回的结果是什么?

我们去对照先行发生原则一个一个对比。

首先是程序次序规则,这里是多线程,不在一个线程中,不适用;

然后是管程锁定规则,这里没有 synchronized,自然不会发生 lock 和 unlock,不适用;

后面对于线程启动规则、线程终止规则、线程中断规则也不适用,这里与对象终结规则、传递性规则也没有关系。

所以说 B 返回的结果是不确定的,也就是说在多线程环境下该操作不是线程安全的。

如何修改呢,一个是对 get/set 方法加入 synchronized 关键字,可以使用管程锁定规则;要么对 value 加 volatile 修饰,可以使用 volatile 变量规则。

通过上面的例子可知,一个操作时间上先发生并不代表这个操作先行发生,那么一个操作先行发生是不是代表这个操作在时间上先发生?

也不是,如下面的例子:

int i = 2;
int j = 1;

在同一个线程内,对 i 的赋值先行发生于对 j 赋值的操作,但是代码重排序优化,也有可能是 j 的赋值先发生,我们无法感知到这一变化。

5.总结:

Java 语言中有一个“先行发生原则”,是判断数据是否存在竞争、线程是否安全的主要依据。 时间先后顺序与先行发生原则之间基本没有太大关系。我们衡量并发安全的问题的时候不要受到时间先后顺序的干扰,一切以先行发生原则为准。

正文到此结束