了解不安全的发布

Understanding unsafe publication

在 JCIP 16.2 B.Goetz 中提到

If you do not ensure that publishing the shared reference happens-before another thread loads that shared reference, then the write of the reference to the new object can be reordered (from the perspective of the thread consumign the object) with writes to its fields.

所以我猜这意味着即使发布 NotThreadSafe 对象也足够了。考虑以下共享对象

public ObjectHolder{
    private int a = 1;
    private Object o = new Object();
    //Not synchronizaed GET, SET
}

//Assume that the SharedObjectHolder published 
//with enough level of synchronization
public class SharedObjectHolder{
    private ObjectHolder oh;
    private final Lock lock = new ReentrantLock();

    public SharedObjectHolder(){
         lock.lock();
         try{
             oh = new ObjectHolder();
         } finally {
             lock.unlock();
         }
     }

     public ObjectHolder get(){
         lock.lock();
         try{
             return oh;
         } finally {
             lock.unlock();
         }
     }
}

现在我们在写入 oh 和从方法 get() 返回 oh 之间有 happens-before。它保证任何调用者线程观察 oh 的最新值。

但是,在构造期间写入 oh 字段(private int aprivate Object o)不是 happens-before 写入 oh。 JMM 不保证这一点。如果我错了,请向 JMM 提供证明参考。因此,即使有这样的发布,读取 oh 的线程可能会观察到一个 部分构造的 对象。

那么,他说我在报价中提供是什么意思?你能澄清一下吗?

如果您只根据上述方法读取或写入 oh,那么 get() 获取的锁将确保您看到所有操作,直到释放 SharedObjectHolder 的构造函数中的锁——包括any 写入 oh 的字段。您所依赖的先行边缘与写入 oh 无关,而与释放锁之前发生的写入(包括写入 oh 的字段)有关,这发生在获取锁之前,这发生在读取之前。

可能会看到部分构造的 oh,如果您有一个线程重新排序 get() 在构造函数和写入之前发生到 oh 发生在他们俩之前。这就是需要安全发布 SharedObjectHolder 实例的原因。

(也就是说,如果您可以安全地发布 SharedObjectHolder,我不明白您为什么不能安全地发布原始 oh 引用。)

我们有:

  1. 写入 ObjectHolder 值
  2. 写哦
  3. 解锁锁
  4. 锁之锁
  5. 读取 oh 和 ObjectHolder 值。

1、2、3 和 4、5 之间存在 happens-before 关系,因为它们按程序顺序且在同一线程中。

因为有锁,3和4之间存在happens-before关系。

因此,由于传递性,ObjectHolder 值的写入与另一个线程中的读取之间存在先行关系。

因为你特别要求反驳你的陈述:“但是,在构造期间写入 oh 字段(private int aprivate Object o)不是 happens-before写入 oh。 JMM 不保证”,看看JLS §17.4.5. Happens-before Order,对第一个项目符号:

If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.

  • If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

这与 happens-before 关系的传递性一起,是 JMM 最重要的保证,因为它意味着我们可以让线程在不同步的情况下执行一系列操作并且仅在需要时同步。但请注意,在写入 ObjectHolder 字段和写入 SharedObjectHolder.oh 之间建立 happens-before 关系并不相关,因为这一切都会发生在单个线程中。

上面引用的重要结果是,由于程序顺序,所有三个写入和释放 Lock 之间存在 happens-before 关系.由于 Lock 的释放与 SharedObjectHolder.get() 中的另一个线程随后获取 Lock 之间也存在 happens-before 关系,传递性在所有三个写入和 Lock 的获取之间建立了 happens-before 关系。这三个写入实际执行的顺序无关紧要,唯一重要的是所有三个都在获取 Lock 时完成。


作为旁注,您在代码注释中写道“假设 SharedObjectHolder 以足够的同步级别发布”。如果我们假设,整个 Lock 变得过时,因为用于正确发布 SharedObjectHolder 实例的“足够同步级别”也足以发布嵌入式 ObjectHolder 及其字段,因为它们的所有初始化 发生在 由于程序顺序 SharedObjectHolder 发布之前。