happen-before 是 JMM 最外围的概念,所以在理解 happen-before 准则之前,首先须要理解 java 的内存模型。
JMM 内存模型
java 内存模型是共享内存的并发模型,线程之间次要通过读 - 写共享变量来实现隐式通信。java 中的共享变量是存储在内存中的,多个线程由其工作内存,其工作形式是将共享内存中的变量拿进去放在工作内存,操作实现后,再将最新的变量放回共享变量,这时其余的线程就能够获取到最新的共享变量。
从横向去看看,线程 A 和线程 B 就如同通过共享变量在进行隐式通信。这其中有很有意思的问题,如果线程 A 更新后数据并没有及时写回到主存,而此时线程 B 读到的是过期的数据,这就呈现了“脏读”景象。为防止脏读,能够通过同步机制(管制不同线程间操作产生的绝对程序)来解决或者通过 volatile 关键字使得每次 volatile 变量都可能强制刷新到主存,从而对每个线程都是可见的。
重排序
在执行程序时,为了进步性能,编译器和处理器经常会对指令进行重排序。个别重排序能够分为如下三种:如图,1 属于编译器重排序,而 2 和 3 统称为处理器重排序。
这些重排序会导致线程平安的问题,一个很经典的例子就是 DCL 问题。JMM 的编译器重排序规定会禁止一些特定类型的编译器重排序;针对处理器重排序,编译器在生成指令序列的时候会通过插入内存屏障指令来禁止某些非凡的处理器重排序。
(1)编译器优化的重排序。编译器在不扭转单线程程序语义的前提下,能够重新安排语句的执行程序;
(2)指令级并行的重排序。古代处理器采纳了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器能够扭转语句对应机器指令的执行程序;
(3)内存零碎的重排序。因为处理器应用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
举个例子:
因为 A,B 之间没有任何关系,对最终后果也不会存在关系,它们之间执行程序能够重排序。因而能够执行程序能够是 A ->B->C 或者 B ->A->C 执行最终后果都是 3.14,即 A 和 B 之间没有数据依赖性。
什么是 happen-before
JMM 能够通过 happens-before 关系向程序员提供跨线程的内存可见性保障(如果 A 线程的写操作 a 与 B 线程的读操作 b 之间存在 happens-before 关系,只管 a 操作和 b 操作在不同的线程中执行,但 JMM 向程序员保障 a 操作将对 b 操作可见)。
具体的定义为:
1)如果一个操作 happens-before 另一个操作,那么第一个操作的执行后果将对第二个操作可见,而且第一个操作的执行程序排在第二个操作之前。
2)两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要依照 happens-before 关系指定的程序来执行。如果重排序之后的执行后果,与按 happens-before 关系来执行的后果统一,那么这种重排序并不非法(也就是说,JMM 容许这种重排序)。
具体的规定:
(1)程序程序规定:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
(2)监视器锁规定:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
(3)volatile 变量规定:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
(4)传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
(5)start()规定:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的 ThreadB.start()操作 happens-before 于线程 B 中的任意操作。
(6)Join()规定:如果线程 A 执行操作 ThreadB.join()并胜利返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作胜利返回。
(7)程序中断规定:对线程 interrupted()办法的调用后行于被中断线程的代码检测到中断工夫的产生。
(8)对象 finalize 规定:一个对象的初始化实现(构造函数执行完结)后行于产生它的 finalize()办法的开始。
利用程序程序规定(规定 1)存在三个 happens-before 关系:
A happens-before B;
B happens-before C;
A happens-before C。
这里的第三个关系是利用传递性进行推论的。这里的第三个关系是利用传递性进行推论的。
A happens-before B, 定义 1 要求 A 执行后果对 B 可见,并且 A 操作的执行程序在 B 操作之前,但与此同时利用定义中的第二条,A,B 操作彼此不存在数据依赖性,两个操作的执行程序对最终后果都不会产生影响,在不扭转最终后果的前提下,容许 A,B 两个操作重排序,即 happens-before 关系并不代表了最终的执行程序。