happens-before 规则通常对应于多个编译器重排序规则和处理器重排序规则。
序言
1 | class VolatileExample { |
假设线程A
执行writer()
方法,按照volatile
会将v=true
写入内存;线程B
执行reader()
方法,按照volatile
,线程B
会从内存中读取变量v
,如果线程B
读取到的变量v
为true
,那么,此时的变量x
的值是多少呢?
这个示例程序给人的直觉就是x
的值为42
,其实,x
的值具体是多少和JDK
的版本有关,如果使用的JDK
版本低于1.5
,则x的值可能为42
,也可能为0
。如果使用1.5
及1.5
以上版本的JDK
,则x
的值就是42
。
看到这个,就会有人提出问题了?这是为什么呢?其实,答案就是在JDK1.5
版本中的Java
内存模型中引入了Happens-Before
原则。
1 | public class Test { |
1 | //在线程A中初始化线程B |
1 | Thread threadB = new Thread(() - { |
1 | //在线程A中将x变量的值初始化为0 |
1 | public class TestThread { |
【原则一】程序次序规则
在一个线程中,按照代码的顺序,前面的操作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
代码块时,能够获取到线程A
对x
变量的写操作,也就是说,线程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中调用线程B
的join()
方法实现),当线程B
完成后(线程A
调用线程B
的join()
方法返回),则线程A
能够访问到线程B
对共享变量的操作。
参见【示例四】。
【原则七】线程中断规则
对线程interrupt()方法的调用Happens-Before于被中断线程的代码检测到中断事件的发生。
【示例五】,在线程A中中断线程B之前,将共享变量x的值修改为100,则当线程B检测到中断事件时,访问到的x变量的值为100。
【原则八】对象终结规则
一个对象的初始化完成Happens-Before于它的finalize()方法的开始。
运行结果如下所示。
Java 构造方法 对象销毁