Guava: MemoizingSupplier的线程安全性

6

Guava Suppliers类包含MemoizingSupplier:

static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
    final Supplier<T> delegate;
    transient volatile boolean initialized;
    // "value" does not need to be volatile; visibility piggy-backs
    // on volatile read of "initialized".
    transient T value;

    MemoizingSupplier(Supplier<T> delegate) {
      this.delegate = delegate;
    }

    @Override public T get() {
      // A 2-field variant of Double Checked Locking.
      if (!initialized) {
        synchronized (this) {
          if (!initialized) {
            T t = delegate.get();
            value = t;
            initialized = true;
            return t;
          }
        }
      }
      return value;
    }

    @Override public String toString() {
      return "Suppliers.memoize(" + delegate + ")";
    }

    private static final long serialVersionUID = 0;
  }

有人可以解释一下这条评论的意思吗?

“value”字段不需要是volatile的;“initialized”字段的volatile读取可以实现可见性。

“initialized”字段上的volatile如何影响“value”字段呢? 根据此文,我们可能会得到“initialized”和“value”字段的不一致组合(例如true+null)。我错了吗?

1个回答

5
这句话的基本含义是,对于volatile读写来说,value的读写是按照一定顺序进行的,这保证了写入的值可以被读取到。

(简化)证明

如果没有初始化value,程序将执行以下两个语句:

value = t;          //normal write
initialized = true; //volatile write

如果该值已被初始化,程序将执行这两个语句:
if(!initialized) { ... } //volatile read
return value;            //normal read

由于易变语义,您在 volatile 写入和 volatile 读取之间具有 happens-before 关系,并且 return value 中的普通读取保证会看到 value = t 的写入。这是因为普通写入在 volatile 写入之前,而普通读取在 volatile 读取之后。

为什么顺序很重要

例如,如果程序像这样编写:
initialized = true;
value = t;

“return value”可能返回一个空值,因为这里的写操作没有在volatile写操作之前发生,而volatile写操作充当内存屏障,因此它不再受到volatile语义的影响。

另一个讨论和一篇好的文章 - nukie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接