一些Guava内部类型,如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的阅读中看来,其他线程似乎只能保证在final
中看到elementSet
字段的正确值,但由于ElementSet
最终来自AbstractSet
},我不认为那里的所有字段都是final
。
答案 0 :(得分:7)
我并非100%清楚这一点(我确定我们团队中的其他人可以更好地回答这个问题)。那说了几句想法:
HashMultiset
扩展AbstractMultiset
。也就是说,ConcurrentHashMultiset
也扩展了AbstractMultiset
并使用了elementSet()
的实现,因此大概必须可能才能使其成为线程安全的。< / LI>
createElementSet()
的实现。据我所知,如果Set
创建的createElementSet()
是不可变(因为构造它时分配的字段是final
),应该是线程安全的。在至少ConcurrentHashMultiset
的情况下,这似乎是正确的。编辑:我问Jeremy Manson这件事,他说:&#34;你对它的看法似乎很好。它不是线程安全的。如果正在构造的对象在正确的位置具有所有最终字段,那么您应该没问题,但我不会偶然依赖它(请注意,许多实现实际上是不可变的而是真正不可变的)。&#34;
注意:对于使用此模式的ConcurrentHashMultiset
等线程安全集合,创建的对象是有意且真正不可变的(尽管如果AbstractSet
要更改,那可能会发生变化,正如Chris在评论中指出的那样)。