java配置openjdk_OpenJDK修订的Java内存模型

本文深入探讨了Java内存模型(JMM)的细节,特别是在OpenJDK9中的变化。讨论了JMM9如何解决无数据争用问题、超薄问题、非易失性变量的原子性问题以及最终字段问题。强调了性能和原子性保证在多线程编程中的重要性,并介绍了JMM9如何适应现代处理器架构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java配置openjdk

传统的Java内存模型涵盖了Java语言的语义保证。 在本文中,我们将重点介绍其中一些语义,并对它们进行更深入的了解。 我们还将尝试就本文中描述的语义传达更新现有Java内存模型(JMM)的动机。 与将来对JMM的更新有关的讨论在本文中将称为JMM9。

Java内存模型

JSR 133(以下称为JMM-JSR133)中定义的现有Java内存模型指定了共享内存的一致性模型,还有助于为开发人员提供定义,以便它们在表示JMM-JSR133时可以保持一致。 JMM-JSR133规范的目标是确保完善通过内存交互的线程的语义定义,以便进行优化并提供清晰的编程模型。 JMM-JSR133旨在提供定义和语义,以使多线程程序不仅是正确的,而且是高性能的,并且对现有代码库的影响最小。

考虑到这一点,我想遍历某些在JMM-JSR133中过度指定或未指定的语义保证,同时重点介绍与如何在JMM9中进行改进有关的社区讨论。

JMM9-顺序一致-无数据争用问题

JMM-JSR133讨论了有关动作的程序执行。 这样的执行将动作与命令结合起来以描述这些动作之间的关系。 在本文中,我想扩展一些命令和关系,然后讨论什么构成顺序一致的执行。 让我们从“程序顺序”开始-每个线程的程序顺序是一个总顺序,表示该线程将执行所有操作的顺序。 有时并不需要命令所有动作。 因此,我们有一些关系只是部分有序的关系。 例如,“在……之前发生”和“与……同步”是两个部分排序的关系。 当一个动作发生之前-在另一动作之前; 第一个动作不仅对第二个动作可见,而且在第二个动作之前被排序。 这两个动作之间的关系称为事前发生关系。 有时,需要订购一些特殊的动作,这些动作被称为“同步动作”。 易失性的读取和写入,监视锁定和解锁等都是同步操作的示例。 同步动作引起与该动作的“同步”关系。 同步关系是部分顺序,这意味着未包括所有对同步动作。 跨所有同步动作的总顺序称为“同步顺序”,每个执行都有一个同步顺序。

现在让我们讨论顺序一致执行-似乎在其所有读写操作中总以顺序发生的执行被称为顺序一致(SC)。 在SC执行中,读取将始终看到最后一次写入该特定变量所写入的值。 当SC执行不显示“数据争用”时,则该程序称为无数据争用(DRF)。 当程序有两个访问没有按事前发生关系排序时,并且当它们访问同一变量时,至少发生了一次写访问,就会发生数据争用。 用于DRF的SC意味着DRF程序的行为就好像它们是SC。 但是严格支持SC是以性能为代价的-大多数系统将重新排序内存操作以提高执行速度,同时“隐藏”昂贵操作的延迟。 同时,甚至编译器也可以对代码重新排序以优化执行。 为了保证严格的顺序一致性,无法对内存操作或代码优化进行所有此类重新排序,因此会降低性能。 JMM-JSR133已经合并了宽松的排序限制,并且由基础编译器进行了任何重新排序,高速缓存与内存的交互以及该程序无法观察到JIT本身。

注意:昂贵的操作需要花费大量CPU周期才能完成和/或阻止执行管道。

性能是JMM9的重要考虑因素,此外,理想情况下,任何编程语言内存模型都应允许所有开发人员都能利用弱顺序的体系结构内存模型。 有一些成功的实现和示例可以放宽严格的顺序,尤其是在弱顺序的体系结构上。

注意:弱排序是指可以对读取和写入进行重新排序的体系结构,并且需要显式的内存屏障来限制这种重新排序。

JMM9-超薄(OoTA)问题

JMM-JSR133的另一个主要语义是禁止“ Out Of Of Thin Air”(OoTA)值。 “事前发生”模型有时可以创建变量值并“凭空”读取,因为它不包含因果关系要求。 需要注意的重要一点是,原因本身并不采用数据和控件依赖项的概念,正如我们在下面的正确同步的代码示例中所看到的那样,非法写入是由写入本身引起的。

(注意:x和y初始化为'0')-

线程a

线程b

r1 = x;

r2 = y;

如果(r1!= 0)

如果(r2!= 0)

y = 42;

x = 42;

此代码是在一致之前发生的,但实际上不是顺序一致的。 例如-如果r1看到x = 42的写入,而r2看到y = 42的写入,则x和y的值都可以为42,这是数据争用条件的结果。

r1 = x;

y = 42;

r2 = y;

x = 42;

在这里,两个写操作均在读取其变量之前提交,并且读操作将看到相应的写操作,这将导致OoTA结果。

注意:数据争夺可能是由于推测而发生的,最终将使自己变成自我实现的预言。 OoTA的保证是遵守因果关系规则。 当前的想法是,因果关系可以与投机性写作一起打破。 JMM9旨在查找OoTA的原因并完善避免OoTA的方法。

为了禁止OoTA值,某些写操作需要等待其读操作才能避免数据竞争。 因此,禁止OoTA的JMM-JSR133定义正式禁止了OoTA读取。 此正式定义由内存模型的“执行和因果关系要求”组成。 基本上,如果可以执行所有程序动作,那么格式正确的执行就可以满足因果关系要求。

注意:当每次读取都可以看到对同一变量的写入时,格式正确的执行发生在线程内服从,“先发生”和“同步顺序”的一致执行中。

如您可能已经知道的那样,JMM-JSR133定义被严格了以防止OoTA值出现。JMM9旨在识别和更正正式定义,以便进行一些常见的优化。

JMM9-对非易失性变量的易失性操作

首先,什么是“ volatile”关键字? -Java'volatile'保证线程之间的交互,这样,当一个线程写入volatile变量时,该写入不仅是其他线程可见的唯一事物,而且其他线程也看到了写入该线程的线程可见的所有写入内容易失性变量。

现在,非易失性变量会怎样? -非易失性变量没有“ volatile”关键字所保证的交互作用。 因此,编译器可以使用非易失性变量的缓存值代替“易失性”,从而保证将始终从内存中读取“易失性”变量。 事前发生模型可用于将同步访问与非易失性变量绑定在一起。

注意:将任何字段声明为“ volatile”并不意味着涉及锁定。 因此,易失性比使用锁的同步便宜。 但是需要注意的是,方法内部具有多个volatile字段可能比锁定这些方法更昂贵。

JMM9-读和写原子性问题和单词恐惧问题。

JMM-JSR133还保证(例外)共享内存并发算法的读写原子性。 非易失性long和double值例外,其中对其中一个的写操作被视为两个单独的写操作。 因此,可以通过两个单独的32位写入来写入单个64位值,并且在那些写入之一仍未完成的情况下执行读取的线程可能只会看到正确值的一半,从而失去原子性。 这是原子性保证如何依赖底层硬件以及内存子系统的一个示例。 例如,底层的汇编指令应能够处理操作数的大小,以保证原子性;否则,如果必须将读取或写入操作拆分为多个导致破坏原子性的操作(如非原子操作的情况)易失的long和double值)。 同样,如果实现导致一个以上的内存子系统事务,则这将破坏原子性。

注意:始终保证易失的long和double字段以及引用具有读写原子性。

一个位优先于另一个位不是一个理想的解决方案,因为如果除去了64位的例外,那么32位架构就会遭受损失。 如果对64位体系结构进行了惩罚,那么无论何时需要原子性,即使基础硬件仍然可以保证原子操作,您也必须长期引入“ volatile”。 例如:双字段不需要挥发物,因为基础架构或ISA或浮点单元将满足64位宽字段的原子性需求。 JMM9旨在确定硬件提供的原子性保证。

JMM-JSR133是在十多年前编写的。 此后,处理器的位元已经发展起来,而64位已成为主流的处理位元。 这立即突显了JMM-JSR133在64位读写方面所做出的妥协-尽管可以在任何体系结构上使64位值成为原子值,但是仍然需要在某些体系结构上获取锁。 现在,这使得那些体系结构上的64位读写变得昂贵。 如果找不到在32位x86体系结构上合理的64位原子操作的实现,那么原子性将不会改变。

注意:语言设计中存在一个潜在的问题,其中“ volatile”关键字的含义已超载。 对于用户来说,运行时很难确定用户是否将“ volatile”重新获得原子性(因此可以在64位平台上将其剥离),或者难以确定内存顺序。

当谈论访问原子性时,读写操作的独立性是一个重要的考虑因素。 对特定字段的写入不应与对任何其他字段的读取或写入交互。 这种JMM-JSR133保证意味着不需要为了提供顺序一致性而进行同步。 因此,JMM-JSR133保证禁止了一个称为“单词撕裂”的问题。 基本上,当对操作数的更新要以比基础体系结构对其所有操作数可用的粒度更低的粒度进行操作时,我们会遇到“单词撕裂”。 要记住的重要一点是,字共享问题是未为64位long和double提供原子性保证的原因之一。 JMM-JSR133中禁止单词撕裂,并且对于JMM9而言,单词撕裂也将继续保持这种状态。

JMM9-最终现场问题

与其他字段相比,最终字段有所不同。 例如,线程读取带有最终字段“ x”的“完全初始化”对象;在对象“完全初始化”之后,保证读取最终字段的初始化值“ y”。 “正常”非最终字段-“ nonX”。

注意:“完全初始化”表示对象的构造函数完成。

鉴于上述情况,JMM9中可以修复一些简单的问题。 例如:volatile字段-即使实例本身是可见的,也不能保证在构造函数中初始化的volatile字段是可见的。 因此,出现了一个问题-最终字段保证是否应扩展到所有字段初始化,包括volatile? 另外,如果完全初始化的对象的“正常”非最终字段的值不变,我们是否可以将最终字段保证扩展到该“正常”字段。

参考书目

我从这些网站中学到了很多,它们还提供了很好的示例代码序列。 我的文章应该被视为介绍性文章,以下内容更适合于更深入地了解Java内存模型。

  1. JSR 133:JavaTM内存模型和线程规范修订版
  2. Java内存模型
  3. JAVA保证金(&C)
  4. jmm-dev档案
  5. 螺纹和锁
  6. 同步和Java内存模型
  7. 所有访问都是原子的
  8. Java内存模型语用学(成绩单)
  9. 内存壁垒:软件黑客的硬件视图

特别感谢

我要感谢杰里米·曼森(Jeremy Manson)帮助我纠正了各种误解,并为我提供了新的术语更清晰的定义。 我还要感谢Aleksey Shipilev帮助减少了本文草案版本中存在的概念复杂性。 Aleksey还指导我/我们阅读了他的JMM-pragmatics文章,以更深入地了解,澄清和举例。

翻译自: https://www.infoq.com/articles/The-OpenJDK9-Revised-Java-Memory-Model/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

java配置openjdk

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值