为什么发布最终字段是安全的?

Why is publishing final fields safe?

我正在阅读 Java Concurrency in Practice by Brian Goetz。在第 51 页。在其中一个脚注中,他说:

While it may seem that field values set in a constructor are the first values written to those fields and therefore that there are no “older” values to see as stale values, the Object constructor first writes the default values to all fields before subclass constructors run. It is therefore possible to see the default value for a field as a stale value.

所以,final 字段的概念现在对我来说还不是很清楚。考虑示例 class:

public class MyClass{
    private final MyImmutableClass mic;

    public MyClass(){
        mic = MyImmutableClass.empty();
    }
}

根据上面的脚注,mic 字段被 分配了两次 ,一次由 Object的构造函数,一次是 MyClass 的构造函数本身。现在,假设我们不安全地发布了一个 MyClass 对象(例如通过 public 字段):

public final MyClass mc;

谁保证 mc 始终被处于一致状态的任何线程观察到?为什么某些线程不能意外观察到默认值?

据我所知,final字段本身只保证对象构造后不能赋值引用。如果我们声明 mc volatile,那就很清楚了。任何读取该字段的线程都应该直接从内存中读取它。禁止从缓存中读取。

UPD:发布示例:

public static void main(String[] args){
    class MyRunnable implements Runnable(){
        private SomeClass sc;
        public MyRunnable(SomeClass sc){
            this.sc = sc;
        }
        public void run(){
            //do some with sc
        }
    }
    SomeClass sc = getInitialized();
    ExecutorService es = Executors.newFixedThreadPool(10);
    MyRunnable mr = new MyRunnable(sc);
    //submiting mr to es 10 times
    es.awaitTemination();
    es.shutdown();
}

private static SomeClass getInitialized(){
    SomeClass sc = new SomeClass();
    sc. initialize();
    return sc;
}
public class SomeClass
    public MyClass mc;

    public void initialize(){
        mc = new MyClass();
    }
}

一个 SomeClass 实例将跨多个线程发布。某些线程可以观察 mic 字段的默认值吗?

mc 在你的例子中是一个实例变量。这意味着您必须具有包含 mc 的 class 的完全初始化实例,以便任何访问某个实例的 mc 的代码都不会抛出 NullPointerException。所以mc访问的时候肯定会初始化

...the Object constructor first writes the default values to all fields before subclass constructors run...

Object class 构造函数看不到 MyClass 只属于 MyClass 的成员(不是从 Object 继承的)。所以上面的说法是正确的,Objectclass不能实例化成员变量mic.

...According to the above footnote, the mic field is assigned twice, once by the Object's constructor and once by the MyClass's constructor itself...

没有。 Object 构造函数只初始化它的成员变量。然后 MyClass 构造函数将初始化它的 mic。最后,您将拥有 MyClass 实例。因此,即使 mic 是非最终的 mic 也不会被分配两次。

发布示例:代码片段未完成。但是,跨多个线程访问某些内容取决于许多因素,例如 它是否是静态成员?父对象是否在某处被引用为静态成员? , mc 何时何地初始化?(默认值 null 肯定是通过构造函数)。 如果不是静态成员,应该是单例,等等...