2

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

4 回答 4

10

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

Java 内存模型中对 final 字段的初始化安全保证非常强大。它们不仅保证对构造函数中最终字段的写入对任何获得对象共享引用的线程都是可见的(即使该引用是通过数据竞争获得的),而且它们保证构造函数中的任何写入通过该阅读该参考资料时可以看到参考资料。唯一需要注意的是,对正在构建的对象的引用在构建过程中不会逃逸。当然,如果类要在构造之后改变对象,或者为客户提供一种方法来获取对 HashSet 的对象引用,那么所有的赌注都没有了。

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

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

于 2012-10-28T15:33:20.460 回答
2

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

于 2012-10-28T14:40:22.633 回答
1

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

因为stooges是final的,所以保证构造完成后完全初始化。在这篇文章中,引用并解释了以下规则:

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

由于条目是构造过程中添加的,以后没有添加任何元素,因此所有线程都应该能够正确查看stooges集合。

于 2012-10-28T14:36:05.087 回答
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,它们只有在构造之后才能看到,因为对象必须在监视之前构造。

于 2012-10-28T14:39:03.133 回答