执行‘;最新的&x2019;Java'值的保证;s的最终字段是否扩展到间接引用?

执行‘;最新的&x2019;Java'值的保证;s的最终字段是否扩展到间接引用?,java,memory,concurrency,synchronization,final,Java,Memory,Concurrency,Synchronization,Final,Java语言规范定义了中最终字段的语义: final字段的使用模型很简单。在对象的构造函数中设置对象的最终字段。在对象的构造函数完成之前,不要在另一个线程可以看到的地方编写对正在构造的对象的引用。如果遵循这一点,那么当另一个线程看到该对象时,该线程将始终看到该对象最终字段的正确构造版本。它还将看到最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样最新 我的问题是,“最新”保证是否扩展到嵌套数组和嵌套对象的内容 简言之:如果一个线程将可变对象图分配给对象中的最终字段,并且对象图从未更新

Java语言规范定义了中最终字段的语义:

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

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

简言之:如果一个线程将可变对象图分配给对象中的最终字段,并且对象图从未更新,那么所有线程都可以通过最终字段安全地读取该对象图吗

一个示例场景:

  • 线程A构造ArrayList的HashMap,然后将HashMap分配给类“MyClass”实例中的最终字段“myFinal”
  • 线程B看到对MyClass实例的(非同步)引用并读取“myFinal”,访问并读取其中一个ArrayList的内容
  • 在这个场景中,线程B看到的ArrayList的成员是否保证至少和MyClass的构造函数完成时一样是最新的

    我希望澄清Java内存模型和语言规范的语义,而不是像同步这样的替代解决方案。我的理想答案是肯定或否定,并参考相关文本

    更新:

    • 我对Java1.5及以上版本的语义感兴趣,即通过JSR133引入的更新Java内存模型。此更新中引入了对最终字段的“最新”保证
    在这个场景中,是 由线程B看到的ArrayList 保证至少达到 当我的班级 建造师完成了吗

    是的,他们是

    线程第一次遇到引用时需要读取内存。由于构建了哈希映射,其中的所有条目都是全新的,因此对对象的引用是对构造函数完成时对象的最新引用

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

    实际上,这意味着如果不在构造函数之后修改最终哈希映射,则其内容对于所有线程都是常量

    编辑

    我知道我以前在什么地方见过这种担保

    这里有一段描述JSR133的文章

    初始化安全性

    新的JMM还试图提供 初始化安全的新保证 --只要一个对象被正确构造(意味着 对对象的引用不正确 在构造函数被调用之前发布 完成),然后所有线程都将看到 其最终字段的值 在其构造函数中设置, 不管是否 同步用于传递 从一个线程到另一个线程的引用。 此外,任何可以 通过最后一个区域到达 正确构造的对象,例如 对象引用的对象的字段 最后一个字段,也保证是 对其他线程也是可见的。这 表示如果最终字段包含 例如,在中对链接列表的引用 添加到正确的 对其他人可见的引用 线程,以及该线程的内容 构建时的LinkedList将 对其他线程可见,而不显示 同步。结果是一场灾难 显著加强 final的意思是指final字段 可以安全地访问,而无需 同步,以及编译器 可以假定最终字段不会 改变,因此可以优化 多次回迁


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

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

    这是因为映射在分配给公共引用之前已完全初始化。一旦构造函数完成,最终的映射将是最新的。

    我担心的是对ArrayList中对象的陈旧引用,以及编译器的重新排序,例如在ArrayList完全初始化之前在HashMap中提供ArrayList实例。我很想知道在将可变对象图分配给最终字段时提供了什么样的排序保证。@mattbh。如果可变对象在构造函数调用之前就已经存在,那么通常的可见性约束将适用,但是所有线程看到的最终引用将指向同一个对象。不过,在没有同步的情况下,每个线程可能会看到它有点不同。@Alexander。谢谢你的回答。规范中是否有任何内容阻止编译器在初始化这些内部对象的(非最终)字段之前使其引用可用?对于非最终字段,编译器通常可以自由执行此操作。Java规范第17.5节中的FinalFieldExample类说明了如何允许这种重新排序。@mattbh。如果您没有通过与其他线程共享半构造的对象(比如通过在构造函数或初始值设定项中传递
    this
    )来打击自己,那么除了调用线程之外,没有人会看到非final字段的更新。在同一线程的上下文中,它可以看到字段的最新值。
    FinalFieldExample
    显示退出构造函数后可能遇到的问题