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

时间:2015-12-28 07:23:03

标签: java multithreading final

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

  

虽然看起来构造函数中设置的字段值是第一个   写入这些字段的值,因此没有“较旧”   值被视为过时值,Object构造函数首先写入   子类构造函数运行之前的所有字段的默认值。它是   因此可以将字段的默认值视为陈旧   值。

所以,最终字段的概念现在还不清楚。考虑样本类:

public class MyClass{
    private final MyImmutableClass mic;

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

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

public final MyClass mc;

谁保证在一致状态下的任何线程始终都会观察到mc?为什么某个线程不会意外地遵守默认值?

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

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字段的默认值吗?

2 个答案:

答案 0 :(得分:5)

示例中的

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

答案 1 :(得分:1)

  

... Object构造函数首先将默认值写入all   子类构造函数运行之前的字段...

Object类构造函数无法查看仅属于MyClass的{​​{1}}成员(不是从MyClass继承而来的)。因此,上述语句是正确的,Object类无法实例化成员变量Object

  

...根据上面的脚注,麦克风领域分配了两次,   一次是Object的构造函数,一次是MyClass的构造函数   本身...

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

发布示例:代码段未完成。但是,跨多个线程访问某些东西取决于许多事情,例如它是否是静态成员? parent在某处被引用为静态成员? where和当mic被初始化时?(默认值mc肯定是通过构造函数)。 如果不是静态成员,则应该是单身等......