解读 Java 内存模型中 Happens-Before 的 8 个原则
本文分享自华为云社区《【高并发】一文秒懂Happens-Before原则》,作者: 冰 河。
在正式介绍 Happens-Before 原则之前,我们先来看一段代码。
【示例一】
以上示例来源于:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong
这里,假设线程 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 原则。
接下来,我们就结合案例程序来说明 Java 内存模型中的 Happens-Before 原则。
【原则一】程序次序规则
在一个线程中,按照代码的顺序,前面的操作 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。
也就是说,如果线程 B 读取到了 v=true,那么,线程 A 设置的 x = 42 对线程 B 就是可见的。换句话说,就是此时的线程 B 能够访问到 x=42。
其实,Java 1.5 版本的 java.util.concurrent 并发工具就是靠 volatile 语义来实现可见性的。
【原则四】锁定规则
对一个锁的解锁操作 Happens-Before 于后续对这个锁的加锁操作。
例如,下面的代码,在进入 synchronized 代码块之前,会自动加锁,在代码块执行完毕后,会自动释放锁。
【示例二】
我们可以这样理解这段程序:假设变量 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 中执行的一个代码片段,根据【原则五】线程的启动规则,线程 A 启动线程 B 之后,线程 B 能够看到线程 A 在启动线程 B 之前的操作,在线程 B 中访问到的 x 变量的值为 100。
【原则六】线程终结规则
线程 A 等待线程 B 完成(在线程 A 中调用线程 B 的 join()方法实现),当线程 B 完成后(线程 A 调用线程 B 的 join()方法返回),则线程 A 能够访问到线程 B 对共享变量的操作。
例如,在线程 A 中进行的如下操作。
【示例四】
【原则七】线程中断规则
对线程 interrupt()方法的调用 Happens-Before 于被中断线程的代码检测到中断事件的发生。
例如,下面的程序代码。在线程 A 中中断线程 B 之前,将共享变量 x 的值修改为 100,则当线程 B 检测到中断事件时,访问到的 x 变量的值为 100。
【示例五】
【原则八】对象终结原则
一个对象的初始化完成 Happens-Before 于它的 finalize()方法的开始。
例如,下面的程序代码。
【示例六】
运行结果如下所示。
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/12633769b5ebb9b490f7c998e】。文章转载请联系作者。
评论