Happens-Before规则

happens-before 规则通常对应于多个编译器重排序规则和处理器重排序规则。

序言

示例一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}

public void reader() {
if (v == true) {
//x的值是多少呢?
}
}
}

假设线程A执行writer()方法,按照volatile会将v=true写入内存;线程B执行reader()方法,按照volatile,线程B会从内存中读取变量v,如果线程B读取到的变量vtrue,那么,此时的变量x的值是多少呢?

这个示例程序给人的直觉就是x的值为42,其实,x的值具体是多少和JDK的版本有关,如果使用的JDK版本低于1.5,则x的值可能为42,也可能为0。如果使用1.51.5以上版本的JDK,则x的值就是42

看到这个,就会有人提出问题了?这是为什么呢?其实,答案就是在JDK1.5版本中的Java内存模型中引入了Happens-Before原则。

示例二
1
2
3
4
5
6
7
8
9
10
public class Test {
private int x = 0;
public void initX {
synchronized (this) { //自动加锁
if (this.x < 10) {
this.x = 10;
}
} //自动释放锁
}
}
示例三
1
2
3
4
5
6
7
8
//在线程A中初始化线程B
Thread threadB = new Thread(()-> {
//此处的变量x的值是多少呢?答案是100
});
//线程A在启动线程B之前将共享变量x的值修改为100
x = 100;
//启动线程B
threadB.start();
示例四
1
2
3
4
5
6
7
8
9
Thread threadB = new Thread(() - {
//在线程B中,将共享变量x的值修改为100
x = 100;
});
//在线程A中启动线程B
threadB.start();
//在线程A中等待线程B执行完成
threadB.join();
//此处访问共享变量x的值为100
示例五
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//在线程A中将x变量的值初始化为0
private int x = 0;

public void execute() {
//在线程A中初始化线程B
Thread threadB = new Thread(()-> {
//线程B检测自己是否被中断
if (Thread.currentThread().isInterrupted()) {
//如果线程B被中断,则此时X的值为100
System.out.println(x);
}
});
//在线程A中启动线程B
threadB.start();
//在线程A中将共享变量X的值修改为100
x = 100;
//在线程A中中断线程B
threadB.interrupt();
}
示例六
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestThread {

public TestThread() {
System.out.println("构造方法");
}


protected void finalize() throws Throwable {
System.out.println("对象销毁");
}

public static void main(String[] args) {
new TestThread();
System.gc();
}
}

【原则一】程序次序规则

在一个线程中,按照代码的顺序,前面的操作Happens-Before于后面的任意操作。

同一个线程中,程序在前面对某个变量的修改一定是对后续操作可见的。例如【示例一】中的程序x=42会在v=true之前执行。

【原则二】volatile变量规则

对一个volatile变量的写操作,Happens-Before于后续对这个变量的读操作。

对一个使用了volatile变量的写操作,先行发生于后面对这个变量的读操作。

【原则三】传递规则

如果A Happens-Before B,并且B Happens-Before C,则A Happens-Before C

合【原则一】、【原则二】和【原则三】再来看【示例一】程序,此时,我们可以得出如下结论:

(1)x = 42 Happens-Before 写变量v = true,符合【原则一】程序次序规则。

(2)写变量v = true Happens-Before 读变量v = true,符合【原则二】volatile变量规则。

再根据【原则三】传递规则,我们可以得出结论:x = 42 Happens-Before 读变量v=true

【原则四】锁定规则

对一个锁的解锁操作 Happens-Before于后续对这个锁的加锁操作。

【示例二】,假设变量x的值为10,线程A执行完synchronized代码块之后将x变量的值修改为10,并释放synchronized锁。当线程B进入synchronized代码块时,能够获取到线程Ax变量的写操作,也就是说,线程B访问到的x变量的值为10

【原则五】线程启动规则

如果线程A调用线程B的start()方法来启动线程B,则start()操作Happens-Before于线程B中的任意操作。

线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作。

【示例三】线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作,在线程B中访问到的x变量的值为100。

【原则六】线程终结规则

线程A等待线程B完成(在线程A中调用线程Bjoin()方法实现),当线程B完成后(线程A调用线程Bjoin()方法返回),则线程A能够访问到线程B对共享变量的操作。

参见【示例四】。

【原则七】线程中断规则

对线程interrupt()方法的调用Happens-Before于被中断线程的代码检测到中断事件的发生。

【示例五】,在线程A中中断线程B之前,将共享变量x的值修改为100,则当线程B检测到中断事件时,访问到的x变量的值为100。

【原则八】对象终结规则

一个对象的初始化完成Happens-Before于它的finalize()方法的开始。

运行结果如下所示。

Java 构造方法 对象销毁