Java:最终字段冻结可从最终字段到达的对象

标题为“ Core Java Concurrency ”的DZone refcard说明:

设置后,无法更改最终字段值。 将对象引用字段标记为final不会阻止从该字段引用的对象稍后更改。 例如,最终的ArrayList字段不能更改为不同的ArrayList,但可以在列表实例上添加或删除对象。

最终字段冻结不仅包括对象中的最终字段,还包括从这些最终字段可到达的所有对象。

我对第二个陈述并不完全清楚。 这是否意味着如果我在类A的类A中有一个final字段,而后者又有一个Integer类型的final字段,那么只有在bc的最终字段冻结之后,才会对A类实例的最终字段冻结完成发生了什么?

 public class A{ public final B b = new B(); } public class B{ public final Integer c = 10; } 

这是否意味着如果我在类A的类A中有一个final字段,而后者又有一个Integer类型的final字段,那么只有在bc的最终字段冻结之后,才会对A类实例的最终字段冻结完成发生了什么?

我想我会仔细地说,在这种情况下,最终字段冻结意味着当您创建A的实例并安全地发布它时,其他对象永远不会看到b或c的未初始化值。

我还要说当你在A中创建B的实例时,A中的其他初始化代码永远不会看到c的未初始化值。

我遇到关于最终字段冻结的实际问题的一个案例是例如一个包含(可变)HashMap的类,仅用于读取,在构造期间初始化:

 public class DaysOfWeek { private final Map daysOfWeek = new HashMap(); public DaysOfWeek() { // prepopulate my map daysOfWeek.put(0, "Sunday"); daysOfWeek.put(1, "Monday"); // etc } public String getDayName(int dayOfWeek) { return daysOfWeek(dayOfWeek); } } 

这里出现的问题是:假设这个对象是安全发布的,并且假设这里没有同步,那么其他线程调用getDayName()是否安全? 答案是肯定的,因为最终的字段冻结保证了HashMap和从它可以到达的所有内容(这里只是字符串,但可能是任意复杂的对象)在构造结束时被冻结。 [如果你想在构建之后实际修改这个地图,那么你需要围绕读写进行显式同步。]这是一个较长的博客,探讨这个主题,并检查一些像Brian Goetz这样的人的一些有趣回应的评论。

顺便说一下,我是refcard的作者

Java Concurrency in Practice在第16.3节中提到了这一点:

初始化安全性保证对于正确构造的对象,所有线程都将看到构造函数设置的最终字段的正确值,而不管对象的发布方式如何。 此外,通过正确构造的对象的最终字段(例如最终数组的元素或最终字段引用的HashMap的内容)可以到达的任何变量也保证对其他线程可见。 对于具有最终字段的对象,初始化安全性禁止在初始加载对该对象的引用时重新排序构造的任何部分。 构造函数对最终字段的所有写入以及通过这些字段可到达的任何变量在构造函数完成时变为“冻结”,并且获得对该对象的引用的任何线程都保证看到至少一个值作为最新的冻结值。 初始化通过final字段可到达的变量的写入不会在构造后冻结后的操作中重新排序。

对。 接下来是JMM

寻找段落:

当构造函数完成时,对象被认为是完全初始化的。 在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值。

由于构造函数在B类初始化Bc保证冻结之前不会完成

保证比您想象的要强。 最终的字段语义甚至适用于分配给最终字段的可变对象(具有通常的限制)。 因此扩展你的例子以使Ab私有和B可变(但不是外部可变的)。

 public class A { private final B b = new B(); public Integer get() { return bc; } } public class B { public Integer c = 10; } 

在这种情况下,即使在不安全的发布下, A.get也永远不会返回null 。 当然这个例子是完全抽象的,因此毫无意义。 通常,它对于数组(例如在String )和集合很重要。

在其他事情发生之前谈论什么是最终的,真的没有意义。 对于您的程序,一旦创建了对象(实际上从一次分配字段的那一刻起),引用就不能再改变了。 由于B实例是在A实例之前创建的,你可以说c在b之前变为final,但它并不重要。

订单很重要的地方是单个类中有多个最终字段。 如果要在另一个字段的赋值中使用一个final字段的值,则只应访问已初始化的字段。

说实话,“最终现场冻结”句子对我来说没有多大意义。