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

时间:2014-04-03 15:42:17

标签: java lazy-loading guava java-memory-model

一些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

1 个答案:

答案 0 :(得分:7)

我并非100%清楚这一点(我确定我们团队中的其他人可以更好地回答这个问题)。那说了几句想法:

  1. 我不认为我们声称这是(确保)线程安全的任何地方。非线程安全集合,例如HashMultiset扩展AbstractMultiset。也就是说,ConcurrentHashMultiset也扩展了AbstractMultiset并使用了elementSet()的实现,因此大概必须可能才能使其成为线程安全的。< / LI>
  2. 我相信这种方法的线程安全性取决于createElementSet()的实现。据我所知,如果Set创建的createElementSet()不可变(因为构造它时分配的字段是final),应该是线程安全的。在至少ConcurrentHashMultiset的情况下,这似乎是正确的。
  3. 编辑:我问Jeremy Manson这件事,他说:&#34;你对它的看法似乎很好。它不是线程安全的。如果正在构造的对象在正确的位置具有所有最终字段,那么您应该没问题,但我不会偶然依赖它(请注意,许多实现实际上是不可变的而是真正不可变的)。&#34;

    注意:对于使用此模式的ConcurrentHashMultiset等线程安全集合,创建的对象是有意且真正不可变的(尽管如果AbstractSet要更改,那可能会发生变化,正如Chris在评论中指出的那样)。