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

Tah*_*tar 11 java concurrency

标题为" Core Java Concurrency "的DZone refcard 声明:

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

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

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

public class A{

  public final B b = new B();

}

public class B{ 

  public final Integer c = 10;

}
Run Code Online (Sandbox Code Playgroud)

Ale*_*ler 11

这是否意味着如果我在A类类型的A类中有一个final字段,而后者又有一个Integer类型的最终字段,那么只有在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • >> ...最终字段冻结在这种情况下意味着,当您创建A的实例并安全发布时,其他对象将永远不会看到b或c的未初始化值<<-否,实际上“安全发布”不会重新出现这里。唯一的要求:“ this”一定不能脱离构造函数 (2认同)

Vla*_*nov 6

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

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