java – Guava中使用的无锁延迟加载模式是否真的线程安全?

一些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,似乎其他线程只能保证在elementSet中看到正确的值,但是由于ElementSet最终来自AbstractSet,所以我不认为它的所有字段都是final.

最佳答案
我不清楚这一点(我相信我们团队中的其他人可以更好地回答这个问题).话虽如此,有几个想法:

>我不认为我们声称任何地方(保证是)线程安全.非线程安全的集合(如HashMultiset)扩展了AbstractMultiset.也就是说,ConcurrentHashMultiset还扩展了AbstractMultiset并使用了它的elementSet()的实现,所以推测实际上它可能是线程安全的.
>我相信这个方法的线程安全性取决于createElementSet()的实现.如果由createElementSet()创建的Set是不可变的(因为在构造时分配的字段是final),那么它应该是线程安全的.至少在ConcurrentHashMultiset的情况下,这似乎是正确的.

编辑:我问杰里曼·曼森这个问题,他说:“你的看法对我来说似乎很好,它不是线程安全的,如果被建造的对象在正确的地方有所有的最后一个字段,你应该是好的但是我不会偶然地依赖这一点(请注意,许多实现实际上是不可变的,而不是真正的不变的).“

注意:对于使用此模式的ConcurrentHashMultiset的线程安全集合,创建的对象是有意和真正地不可变的(尽管如AbstractSet要更改,可能会改变,正如Chris在注释中所指出的那样).

转载注明原文:java – Guava中使用的无锁延迟加载模式是否真的线程安全? - 代码日志