Java中最终的多线程保证和内存模型之间的关系如何



17.4中定义了内存模型。内存模型。

17.5中给出了final字段多线程保证。最终字段语义。

我不明白为什么这些是单独的部分。

AFAIKfinal和内存模型都提供了一些保证
任何实际的程序执行都必须遵守这两个保证
但现在很清楚final是否保证17.4.8中用于验证因果关系要求的中间执行有效。执行和因果关系要求。

另一个不清楚的时刻是17.5.1。final字段的语义定义了一个新的";特别的";happens-before,与happens-before的不同之处在于内存模型:

发生在排序之前,而其他则发生在排序之前。

如果它们是相同的happens-before,则happens-before不再是偏序(因为它不可传递)
我不明白这怎么不会打破局面。

如果这些是不同的happens-before,那么不清楚17.5中的那个是什么。最终的Field Semantics做到了
17.4中的happens-before。内存模型用于限制读取可以返回的内容:

非正式地,如果在排序以阻止读取之前没有发生任何情况,则允许读取r查看写入w的结果。

但是17.5。最后的字段语义是一个不同的部分。

特殊的"最终字段保证"部分是后来的附加组件。文档有时遵循历史的怪癖——如果在JMM首次发布之前发现了"最终现场保证"问题,文档的结构可能会有所不同。

换言之,你在问"为什么这些东西在一个单独的章节里",也许答案是:";因为它是在后来的java版本中添加的,因此它是在完全不同的时间编写的;一个新的章节大概是添加更多文档的最简单的方式&";。当然,我们谈论的是几十年前的这个时候。

§17.5解释了其目的。报价:

final字段的使用模型很简单:在对象的构造函数中为该对象设置final字段;并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方编写对正在构建的对象的引用。如果这样做,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。它还将看到这些最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样最新。

换句话说,在遥远的过去,你可以这样做:

线程A:

  • 创建一个新对象。构造函数"表现良好">1
  • 将此新对象的引用传递给另一个线程。可能是以不安全的方式

线程B:

  • 接收线程获得正确的ref(要么是因为您在同步时安全地进行了同步,即在关系正确设置之前进行,要么是因为执行不安全,但JMM不能保证不安全的代码无法运行:它仍然可以运行)
  • 它调用该对象的一个方法
  • 所述对象见证了一个未初始化的final标记字段,因为初始化确实发生在线程a中,但在关系存在之前没有发生,重新排序和其他恶作剧意味着这个线程还没有看到它

这太烦人了。不可变类的部分意义在于,您可以或多或少地打印出JMM并启动它。如果您的系统是不可变类型的合并,那么您几乎不需要关心其中的每一个棘手规则。除了,在§17.5 存在之前,它实际上并没有这样做

JMM作为一个通用原则,旨在为任何JVM实现提供尽可能少的"手铐",同时使JVM的开发尽可能简单。这是一条细线——例如,如果JMM简单地说:";JVM可以在任何时候自由地重新订购它想要的任何东西,并在任何时候、在任何持续时间内缓存它想要的东西";,那么,编写能够快速运行代码并符合规范的JVM会"更容易"(JVM impls会更快),但是,编写能够真正实现预期目的的多线程代码几乎是不可能的。另一方面,JMM也可以保证JVM中的重新排序是不可能观察到的,无论环境或体系结构如何。但是JVM会慢得像糖蜜,看看Python及其备受诟病的全局解释器锁。

JMM试图成为一个愉快的妥协。§17.5也是本着同样的精神编写的。

它基本上说:

  • 你可以相信这样一个概念,即任何表现良好的构造都意味着final字段将直接计算出来,而不必担心关系之前会发生什么
  • 但是,您根本不能依赖JVM是如何实现保证的。特别是,我们已经根据Happens Before定义了您可以确切依赖的内容,但它与JMM其他部分所谈论的H-B不同。我们向您保证,良好的构造意味着最终字段不会成为问题,但就我们的保证而言:您不能使用此保证来迫使其他保证退出JMM;例如,你不能用这种机制来为其他东西建立H-B

JMM为JVM impl提供了操作空间。JVM impl是否真的使用它,取决于JVM实现者。换言之,JVM实现者很可能决定通过使用与保证§17.4中H-B内容相同的锁定机制来实现§17.5,因此您可以有效地应用诸如"H-B关系是可传递的"之类的属性。JMM的部分目的是允许JVM impl采用一些截然不同的方法来它所规定的保证实际上是如何得到保证的。这是因为JVM必须被编写,这样它们才能在各种硬件上以与本机代码一样快的速度运行代码,同时仍然是一个并非不可能开发的目标平台。

走钢丝走得很快。这是JMM的主要潜在解释,有时可能是迟钝和怪异的。

[1] 一个"行为良好"的构造函数:

  • 在构造过程中不将自己的引用(this)传递给自己类之外的任何代码
  • 不调用任何自己的实例方法,然后读取自己的字段(或者,特别有问题的是,它可以被子类覆盖,子类的实现使用自己的字段)。基本上:调用任何非final方法都是一个瞬间的"你表现不好"违规行为
  • 在构造过程中,不会将我希望存储在字段中的东西的任何对象引用发送给其他类中的代码。即使在这样做之前它已经将其分配给了最终字段

最新更新