gpt4 book ai didi

Java 内存模型 : Is it safe to create a cyclical reference graph of final instance fields, 全部在同一个线程中分配?

转载 作者:搜寻专家 更新时间:2023-10-30 20:00:11 25 4
gpt4 key购买 nike

比我更了解 Java 内存模型的人可以证实我对以下代码已正确同步的理解吗?

class Foo {
private final Bar bar;

Foo() {
this.bar = new Bar(this);
}
}

class Bar {
private final Foo foo;

Bar(Foo foo) {
this.foo = foo;
}
}

我知道这段代码是正确的,但我还没有完成整个happens-before 数学运算。我确实找到了两个非正式的引用,表明这是合法的,但我有点担心完全依赖它们:

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are. [The Java® Language Specification: Java SE 7 Edition, section 17.5]

另一个引用:

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to "escape" during construction. (See Safe Construction Techniques for examples.) In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor. [JSR 133 (Java Memory Model) FAQ, "How do final fields work under the new JMM?"]

最佳答案

是的,它是安全的。您的代码不会引入数据竞争。因此,它已正确同步。这两个类的所有对象在完全初始化状态下对访问这些对象的任何线程都是可见的。

对于您的示例,这相当 straight-forward to derive formally :

  1. 对于正在构建线程的线程,所有观察到的字段值都需要与程序顺序保持一致。对于这种线程内一致性,在构造Bar 时,传递的Foo 值被正确地观察到并且永远不会null。 (这可能看起来微不足道,但内存模型也调节“单线程”内存排序。)

  2. 对于获取 Foo 实例的任何线程,其引用的 Bar 值只能通过 final 读取 field 。这在读取 Foo 对象的地址和取消引用指向 Bar 实例的对象字段之间引入了解引用顺序。 p>

  3. 如果另一个线程因此能够完全观察到 Foo 实例(在形式上,存在一个内存链),则该线程保证观察到这个 Foo 完全构造,这意味着它的 Bar 字段包含一个完全初始化的值。

请注意,如果实例只能通过 Foo 读取,则 Bar 实例的字段本身是 final 甚至都没有关系。添加修饰符并没有什么坏处,而且可以更好地记录意图,因此您应该添加它。但是,就内存模型而言,即使没有它你也会没事的。

请注意,您引用的 JSR-133 说明书仅描述了内存模型的实现,而不是内存模型本身。在很多方面,它过于严格。有一天,OpenJDK 可能不再与此实现保持一致,而是实现一个不那么严格但仍能满足正式要求的模型。 永远不要针对实现进行编码,始终针对规范进行编码!例如,不要依赖于构造函数之后放置的内存屏障,HotSpot 或多或少就是这样实现它的。这些东西不能保证保留,甚至可能因不同的硬件架构而有所不同。

你不应该让 this 从构造函数中引用 escape 的引用规则也太狭隘了。你不应该让它逃逸到另一个线程。例如,如果您将它交给一个虚拟分派(dispatch)的方法,您将无法再控制实例的最终位置。因此,这是一个非常糟糕的做法!但是,实际上不会分派(dispatch)构造函数,您可以按照您描述的方式安全地创建循环引用。 (我假设您可以控制 Bar 及其 future 的变化。在共享代码库中,您应该严格记录 Bar 的构造函数不得让引用遗漏出。)

关于Java 内存模型 : Is it safe to create a cyclical reference graph of final instance fields, 全部在同一个线程中分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29271194/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com