为什么双重检查锁定需要使用volatile(使用本地临时变量)

时间:2019-06-18 11:17:21

标签: java volatile happens-before

我读过一个类似的问题,关于如何在不使用volatile的情况下仔细检查锁定: Double-checked locking without volatile

以及以下代码:

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}

但是我的问题是,为什么不简单地删除volatile属性。

根据我的经验,volatile是用来告诉编译器不要重新排序“写入field对象”和“构造FieldType”之间的顺序。 (请参阅http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

但是在这种情况下,第二次检查后,field = result应该先于result = computeFieldValue(),然后发生。因此,编译器不得重新排序序列。

因此,field无法“过早”初始化。

那么,我对波动性或之前发生的理解怎么了?

更新

首先,这是一个不正确的示例:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo { 
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) 
      synchronized(this) {
        if (helper == null) 
          helper = new Helper();
      }    
    return helper;
    }
  // other functions and members...
  }

不起作用的最明显原因是

  

初始化Helper对象的写操作和对helper字段的写操作可以完成或顺序混乱

换句话说,helper可能会得到一个不完全构造的Helper对象。

volatile之所以在这里起作用,是因为以下两个含义:

  1. 让其他线程“查看”值更改。
  2. 内存屏障,如下所述:
  

JDK5和更高版本扩展了volatile的语义,以便系统不允许相对于任何先前的读取或写入对volatile的写入进行重新排序,并且对之后的任何读取均不能对volatile的读取进行重新排序或写。

我将field = result = computeFieldValue();result = computeFieldValue(); field = result;分开。那么显然存在先发生关系,对吧?

所以我正在考虑使用field = result = new FieldType()这样的局部临时变量是否可以替代内存屏障效应?

0 个答案:

没有答案