happens-before原则
JMM核心概念。
JDK1.5开始,Java使用新的JSR-133内存模型,JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作(可以是在一个线程之内,也可以是在不同线程之间)之间必须要存在happens-before关系。
和程序员密切相关的happens-before规则:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程后的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
- 传递性:若A happens-before B,且B happens-before C,则A happens-before C
- start()规则:如果线程A执行操作
ThreadB.start()
,那么A线程的ThreadB.start()
操作happens-before于线程B中的任意操作 - join()规则:如果线程A执行操作
ThreadB.join()
并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()
操作成功返回
两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行。happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
《JSR-133: Java Memory Model and Thread Specification》 对happens-before关系的定义:
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
- 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,这种重排序并不非法,JMM允许这种重排序。
【🌰栗子】
double pi = 3.14; // A |
上述操作存在3个happens-before关系:
- A happens-before B
- B happens-before C
- A happens-before C
这三个happens-before关系中,2和3是必需的,1是不必要的,A、B可以执行重排序(并行执行)。
as-if-serial语义
不管如何重排序,单线程程序的执行结果不能改变。编译器、Runtime和处理器都必须遵守as-if-serial语义。
为了遵循as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
总结
happens-before关系本质上和as-if-serial语义是一样的。
- as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变
- as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按照程序的顺序来执行的
- happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按照happens-before指定的顺序来执行的
- as-if-serial语义和happens-before规则这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度
资料:https://www.bilibili.com/video/BV1VN411T7xX/ 强推 河北王校长