Java Concurrency In Practice中,给出以下示例以说明如何创建不可变类:

http://www.javaconcurrencyinpractice.com/listings/ThreeStooges.java

此类具有:private final Set<String> stooges = new HashSet<String>();在其构造函数中初始化:

public ThreeStooges() {
    stooges.add("Moe");
    stooges.add("Larry");
    stooges.add("Curly");
}


并有一种方法

public boolean isStooge(String name) {
    return stooges.contains(name);
}


看看一个名字是否是三个臭皮匠之一。

但是当我这样做时:ThreeStooges ts = new ThreeStooges(),是否可以确保在将其引用设置为stooges之前正确构造对象(即ts的状态正确初始化)?

换句话说,如果我发布此对象,是否有可能某个线程将其视为未正确初始化(即,当通过stooges访问时,它会看到isStooge()为空)?

我的理解是,不可变对象在发布时将被正确构造并正确可见-(因为它使用最终实例变量)。我的理解正确吗?如果是,该类是否仍然不变?

编辑:从我看到的评论看来,很难相信一个对象可以在其构造函数完成之前被其他线程看到。这是一个链接:http://jeremymanson.blogspot.in/2008/05/double-checked-locking.html

最佳答案

这里有很多错误的答案:(

Java内存模型中最终字段的初始化安全保证令人惊讶地强大。它们不仅保证对构造函数中最终字段的写操作对于任何获得对对象的共享引用的线程都是可见的(即使该引用是通过数据竞争获得的),而且它们还保证构造函数中通过该对象进行的任何写操作该参考对于阅读该参考是可见的。唯一需要注意的是,在构建过程中不会逃避对正在构建的对象的引用。当然,如果该类是在构造后对对象进行突变,或者为客户端提供一种获取对HashSet的对象引用的方法,则所有选择都将关闭。

此保证的目的是避免对不可变(在这种情况下,实际上是不可变)对象的状态进行棘手的推理。如果该字段是最终字段,并且除了构造函数中的内容外,没有其他写入到引用对象的状态的操作,就可以完成。

如果这使您的头部受伤,请不要担心。如果您(a)将引用字段设置为私有和最终字段,并且(b)在构造函数之外不修改被引用对象的状态,并且(c)不提供任何让客户端执行此操作的访问权限(例如,没有变异方法,没有暴露字段的吸气剂,等等),就完成了。

09-30 21:25