前言
happens-before是JMM的核心,之所以设计happens-before,主要出于以下两个方面的因素考虑的:1)程序员的角度,JMM内存模型需要易于理解、易于编程;2)编译器和处理器的角度,编译器和处理器希望内存模型对其束缚越少越好,这样就可以根据自己的处理规则进行优化。但是这两个方面其实是相互矛盾的,因为JMM易于编程和理解就意味着对编译器和处理器的束缚就越多。
happens-before定义
基于上面的考虑,设计JMM时采用了一种折中的选择——JMM将需要禁止的重排序分为两类(因为编译器和处理器的优化大部分是重排序,所以JMM的处理的关键也就是重排序了):
- 会改变程序执行结果的重排序
- 不会改变程序执行结果的重排序
对应这两种情况,JMM采用了不同的策略:
- 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序
- 对于不会改变程序执行结果的重排序,JMM对编译器和和处理器不做任何要求(自然,编译器和处理器可以其进行重排序)
所以JMM的设计基于这样一种原则:先保证正确性,在考虑执行效率问题。
说了这么多,与happens-before原则有什么关系呢?从上面可以看到JMM实际上可以看做是操作之间的约束模型,这种约束模型的实现就是我们要提到的happens-before了。happens-before**用来指定两个操作之间的执行顺序**,这两个操作可以在一个线程之内也可以在不同的线程中,所以这种对操作顺序的关系的界定可以为程序员提供内存可见性的保证。具体happen-before的定义如下:
1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。
上面第二句话的意思就是说,如果重排序之后的执行结果与按照原来那种happens-before关系执行的结果一致,那么JMM允许编译器和处理器进行这种重排序。所以可以认为:只要不改变程序的执行结果,编译器和处理器可以随意优化。联系之前提到的as-if-serial语义(保证单线程内的程序执行结果不会改变),现在提到的happens-before则保证正确同步的多线程的执行程序的执行结果不会发生改变。
happens-before规则
总共有六条规则:
- 程序顺序规则:一个线程中的每个操作,happens-before于随后该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的获取
- volatile变量规则:对一个volatile域的写,happens-before于对这个变量的读
- 传递性:如果A happens-before B,B happens-before C,那么A happens-before C
- start规则:如果线程A执行线程B的start方法,那么线程A的ThreadB.start()happens-before于线程B的任意操作
- join规则:如果线程A执行线程B的join方法,那么线程B的任意操作happens-before于线程A从TreadB.join()方法成功返回。
看看start规则,假设这样一种情况,如果线程A在执行线程B的start方法之前修改了一些共享变量的值,那么当线程B执行start方法的时候,会去读取这些修改的共享变量的值(上面规则就是这么规定的),这就意味着线程A对共享变量的修改对线程B可见
下面看看join规则是怎么回事。join方法的本义是等待当前执行的线程终止。假设在线程B终止之前,修改了一些共享变量(完全可能啊),线程A从线程B的join方法成功返回后,就会读取这些修改的共享变量。这样也保证了线程B对共享变量的修改对线程A是可见的。