最终的多线程保证和Java中的内存模型有什么关系?

poq*_*poq 8 java final java-memory-model jls happens-before

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

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

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

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

另一个不清楚的时刻是17.5.1。Final Fields 的语义定义了一个新的“special” ,它与内存模型happens-before中的不同:happens-before

happens-before排序不会与其他happens-before排序传递地关闭。

如果它们相同happens-before,则happens-before不再是偏序(因为它不具有传递性)。
我不明白这怎么不会破坏事情。

如果这些不同happens-before,那么就不清楚 17.5 中的是什么。Final Field Semantics确实如此。17.4中
内存模型用于限制读取可以返回的内容:happens-before

非正式地,如果没有happens-before排序来阻止r读取,则允许读取查看写入的结果。w

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

rzw*_*oot 10

特殊的“最终场保证”部分是后来添加的。文档有时会遵循历史的怪癖 - 如果在 JMM 首次发布之前发现“最终现场保证”问题,文档的结构可能会有所不同。

\n

换句话说,你问“为什么这个东西在一个单独的章节中”,也许答案是:“因为它是在 java 的更高版本中添加的,因此它是在完全不同的时间编写的;一个新的章节大概是添加更多文档的最简单方法”。当然,我们此时谈论的是几十年前。

\n

\xc2\xa717.5 解释了其用途。引用:

\n
\n

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

\n
\n

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

\n

线程A:

\n
    \n
  • 制作一个新对象。构造函数“表现良好” 1
  • \n
  • 将这个新对象的引用传递给另一个线程。可能以不安全的方式。
  • \n
\n

线程B:

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

这非常烦人。不可变类的部分要点是您或多或少可以打印出 JMM 并将其点燃。如果您的系统是不可变类型的合并,那么您几乎不需要关心其中的每一个棘手规则。不过,在 \xc2\xa717.5 存在之前,它实际上并不是这样工作的

\n

JMM 作为一般原则旨在为任何 JVM 实现提供尽可能少的“手铐”,同时使 JVM 的开发尽可能简单。这是一条很好的路线 - 例如,如果 JMM 简单地声明:“JVM 可以随时自由地重新排序它想要的任何内容,并在任何时间、任何它想要的持续时间内缓存它想要的任何内容”,然后编写 JVM根据规范快速运行代码会“更容易”(JVM 实现会更快),但是,编写实际执行您想要的操作的多线程代码变得几乎不可能。另一方面,JMM 还可以保证无论环境或体系结构如何,都无法观察到 JVM 中的重新排序。但这样的话 JVM 就会像糖蜜一样慢,参见 Python 及其饱受诟病的全局解释器锁。

\n

JMM 试图成为愉快的妥协者。而\xc2\xa717.5也是抱着同样的精神来写的。

\n

它基本上说:

\n
    \n
  • 您可以相信这样的概念:任何行为良好的构造都意味着final字段会正常运行,而不必担心发生在关系之前的任何情况。
  • \n
  • 但是,您根本不能依赖JVM如何实现保证。特别是,我们已经根据 Happens-Before 定义了您可以完全依赖的内容,但这与 JMM 其余部分讨论的 HB 不同。我们向您保证,行为良好的构造意味着最终字段不会成为问题,但这就是我们的保证:您不能使用此保证来强制 JMM 中的其他保证;例如,您不能使用此机制作为为其他内容建立 HB 的奇怪方法。
  • \n
\n

JMM 为 JVM 实现提供了操作空间。JVM impl 是否实际使用它取决于 JVM 实现者。换句话说,JVM 实现者很可能决定通过使用与保证 \xc2\xa717.4 中的 HB 内容相同的锁定机制来实现 \xc2\xa717.5,因此您可以有效地应用“HB 关系”等属性是传递性的”。JMM 的部分目的是允许 JVM 实现采取一些完全不同的方法来确保它所规定的保证实际上是如何得到保证的。这是因为必须编写 JVM,以便它们能够在各种硬件上以与本机代码一样快的速度运行代码,同时仍然是一个并非不可能开发的目标平台。

\n

简直就是走钢丝。这是 JMM 的主要根本解释,有时可能是迟钝且奇怪的。

\n

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

\n
    \n
  • 在构造期间不会将其自身的引用 ( this) 传递给其自身类之外的任何代码。
  • \n
  • 不调用任何它自己的实例方法,然后读取它自己的字段(或者,特别有问题的是,它可以被子类覆盖,子类的实现使用它自己的字段)。基本上:调用任何非最终方法都会立即导致“你表现不佳”违规。
  • \n
  • 在构造过程中,不会将我希望存储在字段中的事物的任何对象引用发送到其他类中的代码。即使在这样做之前它已经将其分配给最终字段。
  • \n
\n