Java:“三个傀儡”的例子真的不变吗?

时间:2012-10-28 14:33:02

标签: java immutability

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

4 个答案:

答案 0 :(得分:10)

这里有一大堆错误的答案:(

Java内存模型中最终字段的初始化安全保证非常强大。它们不仅保证对构造函数中的最终字段的写入对获取对象的共享引用的任何线程都是可见的(即使该引用是通过数据争用获得的),但它们保证构造函数中的任何写入通过该引用对于通过该引用的读取是可见的。唯一需要注意的是,施工期间对建筑物的引用不会逃脱。当然,如果类在构造之后要改变对象,或者为客户提供一种方法来获取HashSet的对象引用,那么所有的赌注都会被取消。

此保证的目的是防止需要对不可变(在这种情况下,实际上是不可变的)对象的状态进行棘手的推理。如果该字段是最终的并且除了构造函数中的那些之外没有对引用对象的状态的写入,那么您就完成了。

如果这让你的头部受伤,请不要担心。如果你(a)使引用字段为private和final,并且(b)不修改构造函数之外的引用对象的状态,并且(c)不提供任何允许客户端执行相同操作的访问(例如,没有变异的方法,没有暴露场的吸气剂等),你已经完成了。

答案 1 :(得分:2)

当一个线程在完全初始化之前访问该对象时,我看不到任何可能的情况。您可以将构造函数看作是返回全新引用的普通函数。因为引用是新的,所以我认为在初始化之前没有任何其他线程(创建线程除外)访问它。

答案 2 :(得分:1)

此类是不可变的,因为您不公开任何可以更改stooges集的函数。

因为stooges是最终的,所以保证在构造之后完全初始化。在this post中,引用并解释了以下规则:

  

只能在该对象后看到对象引用的线程   已经完全初始化,保证看到正确   该对象的最终字段的初始化值。

由于在构造期间添加了条目,并且之后没有添加任何元素,因此所有线程都应该能够正确查看stooges集。

答案 3 :(得分:0)

private final Set<String> stooges = new HashSet<String>();

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

此代码是不可变的,因为集合stooges已声明为final。但是,这里只有类是不可变的而不是集合,因为元素可以由同一个类随时添加。此处的集合只能构建一次,您无法更改references

在可见性的情况下,由于集合为private,因此它们不可见。如果你使它public,它们只能在构造之后被看到,因为必须在被监视之前构造对象。