12

Java 语言规范在17.5 节中定义了 final 字段的语义:

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

我的问题是——“最新”保证是否扩展到嵌套数组和嵌套对象的内容?

简而言之:如果一个线程将可变对象图分配给对象中的 final 字段,并且对象图永远不会更新,那么所有线程都可以通过 final 字段安全地读取该对象图吗?

一个示例场景:

  1. 线程 A 构造 ArrayLists 的 HashMap,然后将 HashMap 分配给类“MyClass”实例中的最终字段“myFinal”
  2. 线程 B 看到对 MyClass 实例的(非同步)引用并读取“myFinal”,并访问并读取其中一个 ArrayLists 的内容

在这种情况下,线程 B 看到的 ArrayList 的成员是否保证至少与 MyClass 的构造函数完成时一样最新?

我正在寻找对 Java 内存模型和语言规范语义的澄清,而不是像同步这样的替代解决方案。我梦寐以求的答案是肯定或否定,并参考相关文本。

更新:

  • 我对 Java 1.5 及更高版本的语义感兴趣,即通过 JSR 133 引入的更新的 Java 内存模型。此更新中引入了对最终字段的“最新”保证。
4

2 回答 2

7

在这种情况下,线程 B 看到的 ArrayList 的成员是否保证至少与 MyClass 的构造函数完成时一样最新?

是的,他们是。

线程第一次遇到引用时需要读取内存。因为哈希映射是构造的,其中的所有条目都是全新的,所以对对象的引用是up-to-date构造函数完成时的内容。

在初次相遇之后,将应用通常的可见性规则。因此,当其他线程更改最终引用中的非最终字段时,其他线程可能看不到该更改,但它仍然会看到来自构造函数的引用。

实际上,这意味着如果您在构造函数之后不修改最终的 hash-map,则其内容对于所有线程都是常量。

编辑

我知道我以前在某个地方看到过这个保证。

这是本文中描述 JSR 133的有趣段落

初始化安全

新的 JMM 还寻求为初始化安全提供新的保证——只要正确构造了一个对象(意味着在构造函数完成之前不发布对该对象的引用),那么所有线程都将看到它的最终字段在其构造函数中设置,无论是否使用同步将引用从一个线程传递到另一个线程。此外,可以通过正确构造的对象的最终字段访问的任何变量,例如由最终字段引用的对象的字段,也保证对其他线程也是可见的。这意味着如果 final 字段包含对 LinkedList 的引用,除了引用的正确值对其他线程可见之外,该 LinkedList 在构造时的内容也将在没有同步的情况下对其他线程可见。结果显着增强了 final 的含义—— final 字段可以在没有同步的情况下安全地访问,并且编译器可以假设 final 字段不会改变,因此可以优化多个提取。

于 2010-05-13T23:06:33.697 回答
1

如果构造函数是这样写的,你应该没有问题:

public class MyClass {
    public final Map myFinal;
    public MyClass () {
        Map localMap = new HashMap();
        localMap.put("key", new ArrayList());
        this.myFinal = localMap;
    }
}

这是因为地图在分配给公共参考之前已完全初始化。构造函数完成后,最终的 Map 将是最新的。

于 2010-05-14T00:19:34.207 回答