【问题标题】:Understanding unsafe publication了解不安全的出版物
【发布时间】:2016-05-03 06:30:12
【问题描述】:

在 JCIP 16.2 B.Goetz 中提到

如果您不确保发布共享参考 发生-在另一个线程加载该共享引用之前,然后 对新对象的引用的写入可以重新排序(从 线程消耗对象的视角)并写入其 字段。

所以我猜这意味着即使发布带有同步的 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();
         }
     }
}

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

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

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

【问题讨论】:

  • 为什么你需要一个锁在构造函数中(或者实际上是get())?只需将oh 设为final,然后赋值发生在构造函数返回之前。
  • 那么你认为锁会保证在构造函数完成之前无法执行#get方法吗?好笑
  • @AdamSkywalker 我不能只做最后...

标签: java multithreading shared-memory


【解决方案1】:

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

可以看到部分构造的oh,如果你有一个线程重新排序get() 发生在构造函数之前并且写入oh 发生在两者之前他们。这就是为什么 SharedObjectHolder 实例需要安全发布的原因。

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

【讨论】:

    【解决方案2】:

    由于您特别要求对您的陈述进行反驳:“但是,在构造期间写信给oh 字段(private int aprivate Object o)并不是写给happens-beforeoh。 JMM 不保证”,请看JLS §17.4.5. Happens-before Order,对第一个项目符号:

    如果我们有两个动作xy,我们写hb(x, y)来表示x发生了——在你之前

    • 如果 xy 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,然后 hb(x, y)

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

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


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

    【讨论】:

      【解决方案3】:

      我们有:

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

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

      由于锁,3 和 4 之间存在发生前的关系。

      因此,由于传递性,ObjectHolder 值的写入和其他线程中的读取之间存在发生前的关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 2021-11-26
        • 2021-09-02
        • 1970-01-01
        • 2015-07-07
        相关资源
        最近更新 更多