了解不安全的出版物

时间:2016-05-03 06:30:13

标签: java multithreading shared-memory

在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();
         }
     }
}

现在,我们在撰写oh并从方法oh返回get()之前发生在之前。它保证任何调用者线程都会观察到oh的最新值。

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

那么,他说我提供的报价是什么意思?你能说清楚吗?

3 个答案:

答案 0 :(得分:2)

如果您只按照上述方法读取或写入oh,则get()获取的锁将确保您在SharedObjectHolder的构造函数中查看锁定释放之前的所有操作 - 包括任何写入到oh的字段。你所依赖的发生之前的边缘与写oh无关,而且在发布锁定之前发生的所有与写入(包括oh的字段)有关的事情都发生在在获取锁之前发生,这在读取之前发生。

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

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

答案 1 :(得分:0)

我们有:

  1. 写入ObjectHolder值
  2. 写哦
  3. 解锁
  4. 锁定锁定
  5. 读取oh和ObjectHolder值。
  6. 在1,2,3和4之间存在关系,因为它们是按程序顺序并且在同一个线程中。

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

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

答案 2 :(得分:0)

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

  

如果我们有两个动作 x y ,我们会写 hb(x,y)来表示 x发生 - 在y 之前。

     
      
  • 如果 x y 是同一个帖子的操作, x 按照程序顺序出现在 y 之前,然后 hb(x,y)
  •   
     

...

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

上面引用的重要结果是,由于程序顺序,在所有三次写入和Lock的释放之间存在发生在之前的关系。由于Lock的释放与Lock内的另一个线程后续获取SharedObjectHolder.get()之间存在发生之前关系,因此传递性建立在所有三次写入和Lock的获取之间发生之前的关系。这三个写操作的实际执行顺序无关紧要,唯一重要的是所有三个都是在获得Lock时完成的。

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