深入理解Java内存模型(七)——总结

Posted on

深入理解Java内存模型(七)——总结

分享到

百度分享

促进软件开发领域知识与创新的传播

登录

164,153 六月 独立访问用户

特别专题语言 & 开发

Juergen Fesslmeier谈端到端的JavaScript开发

Juergen谈论了使用JavaScript进行端对端开发的好处和开发团队可能遇见的挑战。他还谈到了Wakanda Studio以及如何仅用JavaScript通过它来开发复杂的应用程序。 浏览所有语言 & 开发

特别专题架构 & 设计

设计模式自动化

尽管维护每行代码的成本如此高昂,但我们仍然每天都在编写着大量的样板代码。如果我们有更智能的编译器,那其中很大一部分是可以避免的。实际上,多数模板代码只是重复地实现那些我们已理解透彻的设计模式,只要我们教会编译器一些技巧,有一些设计模式完全是可以自动实现的。 浏览所有架构 & 设计

特别专题过程 & 实践

成功的根本—集成的ALM工具

典型的软件交付项目会无数次地去获取需求,并在多个地方描述测试,但它们却与某一特定的构建里的具体内容并不相符合,因此项目往往需要大量分析来获知谁在做什么以及为什么做。Dave West深入研究造成该问题的原因,并致力研究一个整体的、集成的ALM方法。 浏览所有过程 & 实践

特别专题运维 & 基础架构

书评:验收测试驱动开发实践指南

《验收测试驱动开发实践指南》一书的目的是作为一个介绍性使用指南指导那些从零开始的团队成功执行和应用验收测试驱动开发(ATDD)。尽管该书在指出及总结了成功敏捷测试人员应该掌握的多个测试相关实践上做了有效的工作,但该书最终并没有为它的各层读者提供他们所需要的信息。By Manuel Pais 浏览所有运维 & 基础架构

特别专题企业架构

设计指尖上的世界:移动用户界面一瞥

对任何成功的移动应用来说,用户界面(UI)是至关重要的组成部分。在这篇文章中,Forrest Skull展示了他与人机交互(HCI)研究者们进行的访谈与讨论,他们探讨了移动设备UI方面的原则,以及其它一些正在研究的领域,包括多设备、隐私、安全和语音。这篇文章还描述了开发移动设备用户界面过程中会面临的挑战。 浏览所有企业架构 QCon上海2013 11月1-3日 上海光大会展中心

New UI 您目前处于: InfoQ首页 文章 深入理解Java内存模型(七)——总结

深入理解Java内存模型(七)——总结

作者 程晓明 发布于 三月 15, 2013 | 4 评论

顺序一致性内存模型是一个理论参考模型,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语义,处理器不会对存在数据依赖性的两个内存操作做重排序)。

下面的表格展示了常见处理器内存模型的细节特征: 内存模型名称

对应的处理器 相关厂商内容

JavaOne上海:迁移Spring应用到Java EE 6

JavaOne上海:淘宝的鹰眼分布式日志系统

豆瓣工程副总裁段念确认参与QCon上海2013,担任团队文化专题出品人

白皮书下载:用HTML5 Builder构建单一代码库的Web/移动应用

首届QCon上海20个专题确认,80余场分享,全面征集演讲主题

相关赞助商

JavaOne大会独家社区合作,InfoQ用户享75折购票。 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在不同处理器内存模型中需要插入的内存屏障的示意图:

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

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

JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型,处理器内存模型和顺序一致性内存模型的强弱对比示意图:

从上图我们可以看出:常见的4种处理器内存模型比常用的3中语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。同处理器内存模型一样,越是追求执行性能的语言,内存模型设计的会越弱。

JMM的设计

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

  • 程序员对内存模型的使用。程序员希望内存模型易于理解,易于编程。程序员希望基于一个强内存模型来编写代码。
  • 编译器和处理器对内存模型的实现。编译器和处理器希望内存模型对它们的束缚越少越好,这样它们就可以做尽可能多的优化来提高性能。编译器和处理器希望实现一个弱内存模型。

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

为了具体说明,请看前面提到过的计算圆面积的示例代码: double pi = 3.14; //A double r = 1.0; //B double 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要求禁止的重排序分为了下面两类:

  • 会改变程序执行结果的重排序。
  • 不会改变程序执行结果的重排序。

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

  • 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
  • 对于不会改变程序执行结果的重排序,JMM对编译器和处理器不作要求(JMM允许这种重排序)。

下面是JMM的设计示意图:

从上图可以看出两点:

  • JMM向程序员提供的happens- before规则能满足程序员的需求。JMM的happens- before规则不但简单易懂,而且也向程序员提供了足够强的内存可见性保证(有些内存可见性保证其实并不一定真实存在,比如上面的A happens- before B)。
  • JMM对编译器和处理器的束缚已经尽可能的少。从上面的分析我们可以看出,JMM其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。比如,如果编译器经过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁可以被消除。再比如,如果编译器经过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器可以把这个volatile变量当作一个普通变量来对待。这些优化既不会改变程序的执行结果,又能提高程序的执行效率。

JMM的内存可见性保证

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

  1. 单线程程序。单线程程序不会出现内存可见性问题。编译器,runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
  2. 正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
  3. 未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。

下图展示了这三类程序在JMM中与在顺序一致性内存模型中的执行结果的异同:

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

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

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

  • 增强volatile的内存语义。旧内存模型允许volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序,使volatile的写-读和锁的释放-获取具有相同的内存语义。
  • 增强final的内存语义。在旧内存模型中,多次读取同一个final变量的值可能会不相同。为此,JSR-133为final增加了两个重排序规则。现在,final具有了初始化安全性。

参考文献

  1. Computer Architecture: A Quantitative Approach, 4th Edition
  2. Shared memory consistency models: A tutorial
  3. Intel® Itanium® Architecture Software Developer’s Manual Volume 2: System Architecture
  4. Concurrent Programming on Windows
  5. JSR 133 (Java Memory Model) FAQ
  6. The JSR-133 Cookbook for Compiler Writers
  7. Java theory and practice: Fixing the Java Memory Model, Part 2

关于作者

程晓明,Java软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程,就职于富士通南大。个人邮箱:asst2003@163.com

相关内容

您好,陌生人!

您需要 注册一个InfoQ账号 或者 登录 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p 当有人回复此评论时请E-mail通知我

社区评论 Watch Thread

看完后有两点疑问,请教一下: by Z CS Posted 21/03/2013 07:37 Re: 看完后有两点疑问,请教一下: by 程 晓明 Posted 23/03/2013 12:50

编译器什么时候插入内存屏障? by 黄 春 Posted 10/04/2013 12:49 Re: 编译器什么时候插入内存屏障? by 程 晓明 Posted 14/04/2013 11:01

看完后有两点疑问,请教一下: 21/03/2013 07:37 by Z CS

  1. 文中所写的 “未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。”,JSR-133 真的做到这样了么? 那 么 long和double的 读写 的非原子性是怎么回事啊?是不是矛盾啊?
  2. 关于本文中最后“JSR-133为final增加了两个重排序规则。现在,final具有了初始化安全性。”,这句话,是不是也还要加上那个 前提“保证final引用不从构造函数内逸出”,final才能具有“初始化安全性”啊?

Re: 看完后有两点疑问,请教一下: 23/03/2013 12:50 by 程 晓明

谢谢您的关注。 ////////////////////////////////////////// 问题1 最小安全性与64位数据的非原子性读/写并不矛盾。 它们是两个不同的概念,它们“发生”的时间点也不同。 最小安全性保证对象默认初始化之后(设置成员域为0,null或false),才会被任意线程使用。 最小安全性“发生”在对象被任意线程使用之前。 64位数据的非原子性读/写“发生”在对象被多个线程使用的过程中(读/写共享变量)。 当发生《本文三》末尾的那种问题时(处理器B看到仅仅被处理器A“写了一半“的无效值), 这里虽然处理器B读取到一个被写了一半的无效值,但这个值任然是处理器A写入的,只不过处理器A还没有写完而已。 最小安全性保证线程读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。 但最小安全性并不保证线程读取到的共享变量的值,一定是某个线程写完后的值。 最小安全性保证线程读取到的值不会无中生有的冒出来,但并不保证线程读取到的值一定是正确的。 /////////////////////////////////////* 问题2 是的,你的理解是对的。 这句话也要加上那个前提:“保证final引用不从构造函数内逸出”,final才能具有“初始化安全性”。

编译器什么时候插入内存屏障? 10/04/2013 12:49 by 黄 春

厚积薄发之做。 明白了很多不解的地方。 这里有个问题想问下。 文中说“java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序”。 这里有个问题请教。 插入内存屏障, 是在编译成字节码的时候完成, 还是在JIT执行的时候重新调整指令生成的? 或者JIT只生产本地代码, 跟插入内存屏障之类的没关系?

Re: 编译器什么时候插入内存屏障? 14/04/2013 11:01 by 程 晓明

Doug Lea在《The JSR-133 Cookbook for Compiler Writers》中,并没有明确指定插入内存屏障的时机。 个人估计,应该是取决于具体的JVM实现。

关闭

** by

发布于

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p 当有人回复此评论时请E-mail通知我

关闭 主题 您的回复

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p 当有人回复此评论时请E-mail通知我 关闭

深度内容

Juergen Fesslmeier 七月 02, 2013

设计模式自动化

Gael Fraiteur and Yan Cui 七月 01, 2013

设计指尖上的世界:移动用户界面一瞥

Forrest Shull 六月 28, 2013

成功的根本—集成的ALM工具

Dave West 六月 28, 2013

书评:验收测试驱动开发实践指南

Manuel Pais 六月 26, 2013

跨终端的web

舒文亮 六月 26, 2013

赞助商链接

InfoQ每周精要

通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。

语言 & 开发

Juergen Fesslmeier谈端到端的JavaScript开发

MobileCloud for TFS支持测试Windows Phone,Android,iOS及BlackBerry应用

百度技术沙龙第39期回顾:前端快速开发实践(含资料下载)

架构 & 设计

内存与本机代码的性能

设计模式自动化

连接设备编程 过程 & 实践

成功的根本—集成的ALM工具

ThoughtWorks全球CEO郭晓谈软件人才的招聘与培养

书评:验收测试驱动开发实践指南

运维 & 基础架构

在传统企业中引入DevOps

安全性——“DevOpS”中的S

书评:验收测试驱动开发实践指南 企业架构

设计指尖上的世界:移动用户界面一瞥

Stratos 2.0已发布,支持所有运行时环境和30个IaaS

让1.5亿移动端用户第一时间获取消息

通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。

特别专题

语言 & 开发 架构 & 设计 过程 & 实践 运维 & 基础架构 企业架构 提供反馈

feedback@cn.infoq.com 错误报告

bugs@cn.infoq.com 商务合作

sales@cn.infoq.com 内容合作

editors@cn.infoq.com InfoQ.com及所有内容,版权所有 © 2006-2013 C4Media Inc. InfoQ.com 服务器由 Contegix提供, 我们最信赖的ISP合作伙伴。 隐私政策

Close E-mail 密码

使用Google账号登录 使用Microsoft账号登录 忘记密码? InfoQ账号使用的E-mail 发送邮件

重新登录 重新发送激活信息 重新发送

重新登录 没有用户名?

点击注册

希望本站内容对您有点用处,有什么疑问或建议请在后面留言评论
转载请注明作者(RobinChia)和出处 It so life ,请勿用于任何商业用途