Java内存模型之happens-before概念详解
简介
happens-before是jmm的核心概念。理解happens-before是了解jmm的关键。
1、设计意图
jmm的设计需要考虑两个方面,分别是程序员角度和编译器、处理器角度:
- 程序员角度,希望内存模型易于理解、易于编程。希望是一个强内存模型。
- 编译器和处理器角度,希望减少对它们的束缚,以至于编译器和处理器可以做更多的性能优化。希望是一个弱内存模型。
因此jsr-133专家组设计jmm的核心目标就两个:
为程序员提供足够强的内存模型对编译器和处理器的限制尽可能少
下面通过一段代码来看jsr-133如何实现这两个目标:
double pi = 3.14; //a double r = 1.0; //b double area = pi * r * r //c
上述代码存在如下happens-before关系:
- a happens-before b
- b happens-before c
- a happens-before c
这3个happens-before关系中,第二个和第三个是必须的,而第一个是非必须的(a、b操作之间重排序,程序执行结果不会发生改变)。
jmm把happens-before要求禁止的重排序分为下面的两类:
- 会改变程序执行结果的重排序
- 不会改变程序执行结果的重排序
jmm对这两种不同性质的重排序,采取了不同的策略:
- 对于会改变程序执行结果的重排序,jmm要求编译器和处理器必须禁止
- 对于不会改变程序执行结果的重排序,jmm不做要求(jmm运行)
jmm设计示意图:
总结:
- jmm给程序员提供的happens-before规则能满足程序员的需求。简单易懂,具有足够强的内存可见性保证。
- jmm对编译器和处理器的束缚尽可能少。遵循的原则是:不改变程序的执行结果(正确同步或单线程执行),编译器和处理器可以任意优化。
2、happens-before的定义
起源:
happens-before规则来源于leslie lamport《time, clocks and the ordering of events in a distributed system》。该论文中使用happens-before来定义分布式系统中事件之间的偏序关系(partial ordering),该文中给出了一个分布式算法,能用来将偏序关系扩展为某种全序关系。
java中的应用:
jsr-133使用happens-before来指定两个操作之间的执行顺序。jmm可以通过happens-before关系向程序员提供跨线程的内存可见性保证。
《jsr-133:java memory model and thread specification》对happens-before关系的定义如下:
如果操作a happens-before 操作b,那么a操作的执行结果将会对操作b可见,且操作a的执行顺序排在操作b之前——jmm对程序员的承诺两个操作存在happens-before关系,并不意味着java平台的具体实现必须按照happens-before的顺序来执行。如果重排序不改变程序执行结果(与happens-before)规则一致,那么这种重排序是不非法的(jmm允许这种重排序)。——jmm对编译器和处理器的束缚原则
happens-before和as-if-serial语义:
从上述来看,happens-before和as-if-serial语义本质上是一回事
- as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不改变
- as-if-serial语义给编程者一种单线程是按程序顺序执行的幻境;happens-before关系给编程者一种正确同步的多线程是按照happens-before指定的顺序执行的幻境。
两者的目的都是为了在不改变程序执行结果的前提下,尽可能的提高程序的执行效率。
3、happens-before规则
《jsr-133:java memory model and thread specification》定义了如下happens-before规则
- 程序顺序规则
- 监视器锁规则
- volatile变量规则
- 传递性
- start()规则
- join()规则
3.1 volatile写-读
volatile写-读建立的happens-before关系
分析上图:
- 1 happens-before 2和3 happens-before 4由程序顺序规则产生。由于编译器和处理器遵循as-if-serial语义,也就是说,as-if-serial语义保证了程序顺序规则。因此可以把程序顺序规则看成是对as-if-serial语义的“封装”。
- 2 happens-before 3 是有volatile规则产生。一个volatile变量的读,总是能看到(任意线程)对这个volatile变量的最后写入。
- 1 happens-before 4 是由传递性规则产生的。这里的传递性是由volatile的内存屏障插入策略和volatile的编译器重排序规则来共同保证的。
3.2 start()规则
假设线程a在执行的过程中,通过执行threadb.start()来启动线程b;同时,假设线程a在执行threadb.start()之前修改了一个共享变量,线程b在执行后会读取这些共享变量。
start()程序对应的happens-before关系图:
分析上图:
- 1 happens-before
- 2 由程序顺序规则产生2 happens-before 4 由start规则产生
- 1 happens-before 4 由传递性规则产生
因此线程a执行threadb.start()之前对共享变量所做的修改,在线程b执行后都将确保对线程b可见。
3.3 join()规则
假设线程a执行的过程中,通过执行threadb.join()来等待线程b终止;则线程b在终止之前修改了一些共享变量,线程a从threadb.join()返回后会读这些共享变量。
join()程序的happens-before关系图:
分析上图:
- 2 happens-before
- 4 由join()规则产生4 happens-before 5 由程序顺序规则产生
- 2 happens-before 5 由传递性规则产生
因此线程a执行操作threadb.join()并成功返回,线程b中任意操作都将对线程a可见。
文章总结至《java并发编程艺术》,下篇总结“双重检查所定与延迟初始化”,敬请关注。
以上就是java内存模型之happens-before概念详解的详细内容,更多关于java内存模型 happens-before的资料请关注其它相关文章!