有些番石榴内部类型,如AbstractMultiset,有如下模式:
private transient Set<E> elementSet;
@Override
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = createElementSet();
}
return result;
}
Set<E> createElementSet() {
return new ElementSet();
}这样做的目的是推迟创建集合视图(elementSet(),entrySet()),直到实际需要它们为止。进程周围没有锁定,因为如果两个线程同时调用elementSet(),则可以返回两个不同的值。编写elementSet字段会有一场竞赛,但是由于对引用字段的写入在Java中总是原子化的,所以谁赢得比赛并不重要。
但是,我担心Java内存模型在这里说的是内联。如果createElementSet()和ElementSet的构造函数都是内联的,那么我们似乎可以得到如下内容:
@Override
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = (allocate an ElementSet);
(run ElementSet's constructor);
}
return result;
}这将允许另一个线程观察elementSet的非空值,但未完全初始化值。有什么原因不能发生吗?从我对JLS 17.5的阅读来看,其他线程似乎只能在elementSet中看到final字段的正确值,但是由于ElementSet最终是从AbstractSet派生的,所以我认为不能保证它的所有字段都是final。
发布于 2014-04-03 17:20:41
我对此并不十分清楚(我相信我们团队中的其他人能更好地回答这个问题)。话虽如此,我有几个想法:
HashMultiset扩展AbstractMultiset。也就是说,ConcurrentHashMultiset还扩展了AbstractMultiset,并使用了它的elementSet()实现,因此想必它实际上必须是线程安全的。createElementSet()的实现。据我所知,如果由Set创建的createElementSet()是不可变的(在构造它时分配的字段是final),那么它应该是线程安全的。至少在ConcurrentHashMultiset的情况下,这似乎是正确的。编辑:我问过杰里米·曼森这件事,他说:“你对它的看法对我来说很好。它不安全。如果正在构建的对象在正确的位置有所有最后的字段,你应该没事,但我不会偶然地依赖它(请注意,许多实现实际上是不可变的,而真正是不可变的)。”
注意:对于使用此模式的线程安全集合(如ConcurrentHashMultiset ),创建的对象是有意且真正不可变的(尽管AbstractSet要更改,这可能会改变,正如克里斯在注释中所指出的那样)。
https://stackoverflow.com/questions/22842588
复制相似问题