JCIP中的非最终变量和线程安全性

时间:2011-12-03 03:09:33

标签: java concurrency thread-safety

JCIP中,我们有一段代码如下:

清单4.2:

@ThreadSafe
public class PersonSet {
    @GuardedBy("this")
    private final Set<Person> mySet = new HashSet<Person>(); // line 3

    public synchronized void addPerson(Person p) {
        mySet.add(p);
    }

    public synchronized boolean containsPerson(Person p) {
        return mySet.contains(p);
    }
}

我想知道我们是否将第三行更改为:

   private Set<Person> mySet = new HashSet<Person>(); // line 3, removes final

是否真的说该类不再是线程安全的,因为即使在构造函数退出并且发布了对PersonSet实例的引用之后,非最终变量mySet也可能为null?

例如,这样的调用代码可能会失败,还是我误解了某些事情,这是真的吗? :

PersonSet p = new PersonSet();
SendToThreadB(p);

如果我有一个限制,不允许将字段标记为“final”(如我可能必须将其与新实例交换),那么有什么解决方案可以确保该类仍然是线程安全的使用final

4 个答案:

答案 0 :(得分:2)

如果您希望能够更改字段值,请创建字段volatile,如果您希望对该字段的更改对其他线程可见,而不必使用其他发生在机制。 (读取和写入volatile字段发生在事件之前。)

当然,如果您总是使用其他发生在之前的系统(例如,synchronized,互斥锁,BlockingQueueExchanger,则设置字段值在创建您发送给它的线程之前,等等,然后volatile没有必要。但这更加脆弱,因为如果你以后更改代码,以便发生之前没有发生,那么你就会创建一个bug。

答案 1 :(得分:2)

如果删除final,则在 unsafe publication 之后使用实例时会变得不安全。也就是说,如果另一个线程无法通过synchronized / volatile访问该对象以生成适当的发生在之前的关系,那么它可能会看到部分初始化的对象。在这种情况下,它可能会以相对安全的方式失败,从NullPointerException取消引用mySet字段。理论上,另一个线程可以看到对HashSet的引用,但该对象的部分或全部字段可能尚未设置。

答案 2 :(得分:1)

如果删除final,由于方法上的synchronized修饰符,该类仍然是线程安全的。他们将提供发生之前的关系,以保证每个人都能看到mySet引用的相同值。

关于final的观点是,它使这个变体对于类ifself而言是线程安全的。

public class PersonSet {
    private final Set<Person> mySet = new HashSet<Person>();

    public Set<Person> getSet() {
        return mySet;
    }
}

请注意,getter上没有synchronized修饰符。

(当然,任何获取HashSet对象的代码都需要在对象上同步它们的操作......不知何故......这意味着这不是一个好设计的例子。)

答案 3 :(得分:0)

我认为你是对的 - 你需要final关键字,没有它,另一个线程可以将mySet视为null

除了final字段之外,构造函数没有内存障碍或同步。因此,尽管对HashSet的各种修改都受到同一个监视器(属于this的监视器)的保护,但是该对象对mySet字段的引用的分配没有受到保护。