Guava:MemoizingSupplier 线程安全

Guava: MemoizingSupplier thread safety

Guava Suppliers-class 包含 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" does not need to be volatile; visibility piggy-backs on volatile read of "initialized".

"initialized" 字段上的 volatile 如何影响 "value" 字段? 根据 this 文章,我们可以得到 "initialized" 和 "value" 字段的不一致组合(例如 true+null)。我错了吗?

这句话基本上意味着 value 的读写以某种方式与易失性读写进行排序,以保证写入的值对读取可见。


(简化)证明

如果这个值没有被初始化,程序会执行那两条语句:

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

如果值被初始化,程序将执行这两个语句:

if(!initialized) { ... } //volatile read
return value;            //normal read

多亏了可变语义,可变写入和可变读取之间存在先行关系,return value 中的正常读取保证可以看到 value = t 处的写入。这是有效的,因为正常写入是 before 易失性写入,正常读取是 after 易失性读取。


为什么顺序很重要

例如程序是这样写的:

initialized = true;
value = t;

return value 可以 return 一个空值,因为这里的写入不会发生在充当内存屏障的易失性写入之前,因此它不再受益于易失性语义。