为了安全地发布对象,为什么既需要“将对它的引用存储到最终字段中”又需要“适当构造的对象”?

时间:2019-04-12 08:44:36

标签: java multithreading java-memory-model safe-publication

我正在阅读“实践中的Java并发性”,它说:

  

要安全地发布对象,必须同时使对该对象的引用和该对象的状态对其他线程可见。可以通过以下方式安全地发布正确构造的对象:

     
      
  • 从静态初始值设定项初始化对象引用;
  •   
  • 将对它的引用存储到volatile字段或AtomicReference中;
  •   
  • 将对它的引用存储到适当构造的对象的最终字段中;或
  •   
  • 将对其的引用存储到由锁适当保护的字段中。
  •   

我不理解的是“将对其的引用存储到正确构建的对象的final字段中”,为什么需要“正确构建的对象”? < strong>没有“没有适当构造的对象”,其他线程可以看到处于不一致状态的对象吗?

我已经阅读了几个相关的问题:

但是我找不到为什么需要“适当构造的对象”的很多解释。

下面的示例来自an answer in question "final vs volatile guaranntee w.rt to safe publication of objects - Stack Overflow"

class SomeClass{
    private final SomeType field;

    SomeClass() {
        new Thread(new Runnable() {
            public void run() {
                SomeType copy = field; //copy could be null
                copy.doSomething(); //could throw NullPointerException
            }
        }).start();
        field = new SomeType();
    }
}

SomeClass的构建方式不正确,当然copy可能是null但是我认为该线程无法看到copy不一致的状态,copynull或“对copy的引用,并且copy的状态必须同时显示给线程可见” < / strong>。因此,即使field的构造不正确,也可以安全发布SomeClass。我说的对吗?

希望有人可以给我更多的解释,谢谢。

1 个答案:

答案 0 :(得分:0)

这取决于您所说的“一致状态”。如果看到一个空指针应位于已发布对象的位置,即该对象实际上看起来好像未发布,则算作“一致”,那么您是对的,因为该示例会产生“一致”。

但是请注意,final个字段应该更改其值。如果线程从final读取,则可以安全地假定该字段的值以后不会更改。线程的实现或(JIT)编译器可能会将字段的值“缓存”在某个变量(或寄存器)中,因为final告诉它读取的值保持不变。

具体来说,类似

的代码
new Thread(new Runnable() {
    public void run() {
        while ( field == null ) {
          // Wait a little...
        }
        // Field was initialized, go ahead.
    }
}).start();

可能只有两种可能的结果:要么永远不会进入循环,要么变成无限循环。

这就是为什么在初始化之前先访问final字段 不安全的原因;和final字段只能保证在构造函数完成后 初始化。

如果您在线程的代码中写入标准字段名SomeClass.this.field,则问题可能会变得更加明显。您可以忽略它,但是编译器将为您隐式生成正确的访问权限。使用完全限定的字段名称,您可以更清楚地看到线程在SomeClass.this.field完全初始化之前访问了this。因此,实际上,您实际上并没有发布一致的SomeType对象,而是发布了仍然包含该字段的仍然不一致的SomeClass对象。