java 内存模型-09-jmm 汇总

作者 : 开心源码 本文共3285个字,预计阅读时间需要9分钟 发布时间: 2022-05-12 共267人阅读

解决器内存模型

顺序一致性内存模型是一个理论参考模型,JMM 和解决器内存模型在设计时通常会把顺序一致性内存模型作为参照。

JMM 和解决器内存模型在设计时会对顺序一致性模型做少量放松,由于假如完全按照顺序一致性模型来实现解决器和 JMM,那么很多的解决器和编译器优化都要被禁止,这对执行性能将会有很大的影响。

分类

根据对不同类型读/写操作组合的执行顺序的放松,可以把常见解决器的内存模型划分为下面几种类型:

  1. 放松程序中写-读操作的顺序,由此产生了 total store ordering 内存模型(简称为TSO)。

  2. 在前面1的基础上,继续放松程序中写-写操作的顺序,由此产生了 partial store order 内存模型(简称为PSO)。

  3. 在前面1和2的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了 relaxed memory order 内存模型(简称为RMO)和 PowerPC 内存模型。

注意,这里解决器对读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的(由于解决器要遵守 as-if-serial 语义,解决器不会对存在数据依赖性的两个内存操作做重排序)。

  • 细节

| 内存模型名称 | 对应的解决器 | Store-Load 重排序 | Store-Store 重排序 | Load-Load 和 Load-Store 重排序 | 可以更早读取到其它解决器的写 | 可以更早读取到当前解决器的写 |
| TSO | sparc-TSO X64 | Y | | | | Y |
| PSO | sparc-PSO | Y | | Y | | Y |
| RMO | ia64 | Y | Y | Y | | Y |
| PowerPC | PowerPC | Y | Y | Y | Y | Y |

在这个表格中,我们可以看到所有解决器内存模型都允许写-读重排序,起因在第一章以说明过:它们都使用了写缓存区,写缓存区可能导致写-读操作重排序。

同时,我们可以看到这些解决器内存模型都允许更早读到当前解决器的写,起因同样是由于写缓存区:因为写缓存区仅对当前解决器可见,这个特性导致当前解决器可以比其余解决器先看到临时保存在自己的写缓存区中的写。

上面表格中的各种解决器内存模型,从上到下,模型由强变弱。

越是追求性能的解决器,内存模型设计的会越弱。由于这些解决器希望内存模型对它们的约束越少越好,这样它们即可以做尽可能多的优化来提高性能。

因为常见的解决器内存模型比 JMM 要弱,java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制解决器的重排序。

同时,因为各种解决器内存模型的强弱并不相同,为了在不同的解决器平台向程序员展现一个一致的内存模型,JMM 在不同的解决器中需要插入的内存屏障的数量和种类也不相同。

JMM 屏蔽了不同解决器内存模型的差异,它在不同的解决器平台之上为java程序员呈现了一个一致的内存模型。

JMM,解决器内存模型与顺序一致性内存模型之间的关系

JMM是一个语言级的内存模型,解决器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。

下面是语言内存模型,解决器内存模型和顺序一致性内存模型的强弱比照示用意:

[图片上传失败…(image-60a9e6-1542801451991)]

常见的 4 种解决器内存模型比常用的 3 中语言内存模型要弱,解决器内存模型和语言内存模型都比顺序一致性内存模型要弱。

同解决器内存模型一样,越是追求执行性能的语言,内存模型设计的会越弱

JMM 的设计

设计

从 JMM 设计者的角度来说,在设计 JMM 时,需要考虑两个关键因素:

  • 程序员对内存模型的使用。

程序员希望内存模型易于了解,易于编程。程序员希望基于一个强内存模型来编写代码。

  • 编译器和解决器对内存模型的实现。

编译器和解决器希望内存模型对它们的约束越少越好,这样它们即可以做尽可能多的优化来提高性能。编译器和解决器希望实现一个弱内存模型。

因为这两个因素互相矛盾,所以 JSR-133 专家组在设计 JMM 时的核心目标就是找到一个好的平衡点:
一方面要为程序员提供足够强的内存可见性保证;另一方面,对编译器和解决器的限制要尽可能的放松。

实现

下面让我们看看 JSR-133 是如何实现这一目标的。

为了具体说明,请看前面提到过的计算圆面积的示例代码:

double pi  = 3.14;    //Adouble r   = 1.0;     //Bdouble area = pi * r * r; //C

上面计算圆的面积的示例代码存在三个 happens-before 关系:

  1. A happens-before B

  2. B happens-before C

  3. A happens-before C

因为 A happens-before B,happens-before 的定义会要求:A操作执行的结果要对B可见,且 A 操作的执行顺序排在 B 操作之前。

但是从程序语义的角度来说,对 A 和 B 做重排序即不会改变程序的执行结果,也还能提高程序的执行性能(允许这种重排序减少了对编译器和解决器优化的约束)。

也就是说,上面这 3 个 happens-before 关系中,尽管 2 和 3 是必须要的,但 1 是不必要的。

禁止重排序分类

因而,JMM 把 happens-before 要求禁止的重排序分为了下面两类:

  1. 会改变程序执行结果的重排序

  2. 不会改变程序执行结果的重排序

JMM 对这两种不同性质的重排序,采取了不同的策略:

  1. 对于会改变程序执行结果的重排序,JMM要求编译器和解决器必需禁止这种重排序。

  2. 对于不会改变程序执行结果的重排序,JMM对编译器和解决器不作要求(JMM 允许这种重排序)。

JMM 的设计示用意

JMM 向程序员提供的 happens-before 规则能满足程序员的需求。

  1. JMM 的 happens-before 规则不但简单易懂,而且也向程序员提供了足够强的内存可见性保证(有些内存可见性保证其实并不肯定真实存在,比方上面的 A happens-before B)。

  2. JMM 对编译器和解决器的约束已经尽可能的少。

从上面的分析我们可以看出,JMM 其实是在遵循一个基本准则:

只需不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和解决器怎样优化都行。

比方,假如编译器经过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁可以被消除。

再比方,假如编译器经过细致的分析后,认定一个 volatile 变量仅仅只会被单个线程访问,那么编译器可以把这个 volatile 变量当作一个普通变量来对待。

这些优化既不会改变程序的执行结果,又能提高程序的执行效率。

JMM 的内存可见性保证

Java程序的内存可见性保证按程序类型可以分为下列三类:

1、 单线程程序。

单线程程序不会出现内存可见性问题。编译器,runtime和解决器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。

2、 正确同步的多线程程序。

正确同步的多线程程序的执行将具备顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和解决器的重排序来为程序员提供内存可见性保证。

3、未同步/未正确同步的多线程程序。

JMM 为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。

只需多线程程序是正确同步的,JMM 保证该程序在任意的解决器平台上的执行结果,与该程序在顺序一致性内存模型中的执行结果一致。

JSR-133 对旧内存模型的修补

JSR-133 对 JDK5 之前的旧内存模型的修补主要有两个:

  • 加强 volatile 的内存语义。

旧内存模型允许 volatile 变量与普通变量重排序。JSR-133 严格限制 volatile 变量与普通变量的重排序,使 volatile 的写-读和锁的释放-获取具备相同的内存语义。

  • 加强 final 的内存语义。

在旧内存模型中,屡次读取同一个 final 变量的值可能会不相同。

为此,JSR-133 为 final 添加了两个重排序规则。现在,final 具备了初始化安全性。

More+

FAQ

The JSR-133 Cookbook for Compiler Writers

参考资料

  • jmm

http://www.infoq.com/cn/articles/java-memory-model-7

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » java 内存模型-09-jmm 汇总

发表回复