如何解决Java中的“双重检查锁定已损坏”声明?

我想在Java中为多线程实现惰性初始化。
我有一些这样的代码:

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            Helper h;
            synchronized(this) {
                h = helper;
                if (h == null) 
                    synchronized (this) {
                        h = new Helper();
                    } // release inner synchronization lock
                helper = h;
            } 
        }    
        return helper;
    }
    // other functions and members...
}

我得到了“双重检查锁定已损坏”声明。
我该如何解决这个问题?


答案 1

以下是项目 71 中推荐的成语:明智地使用有效 Java 的惰性初始化

如果您需要在实例字段上使用延迟初始化来提高性能,请使用双重检查习惯用语。此成语避免了在初始化字段后访问字段时锁定的开销(项目 67)。这个成语背后的想法是检查字段的值两次(因此称为双重检查):一次没有锁定,然后,如果字段看起来未初始化,则第二次使用锁定。仅当第二个检查指示字段未初始化时,调用才会初始化该字段。由于字段已初始化,则不会锁定,因此声明字段至关重要(项目 66)。这是成语:volatile

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;

private FieldType getField() {
    FieldType result = field;
    if (result != null) // First check (no locking)
        return result;
    synchronized(this) {
        if (field == null) // Second check (with locking)
            field = computeFieldValue();
        return field;
    }
}

此代码可能看起来有点复杂。特别是,需要局部变量的结果可能不清楚。此变量的作用是确保字段在已初始化的常见情况下仅读取一次。虽然不是严格必要的,但这可能会提高性能,并且根据适用于低级并发编程的标准,它更优雅。在我的机器上,上面的方法比没有局部变量的明显版本快25%左右。

在1.5版本之前,双重检查习语不能可靠地工作,因为易失性修饰符的语义不够强大,无法支持它[Pugh01]。1.5 版中引入的内存模型修复了这个问题 [JLS, 17, Goetz06 16]。如今,双重检查习语是懒惰初始化实例字段的首选技术。虽然您也可以将双重检查习惯用语应用于静态字段,但没有理由这样做:惰性初始化持有者类习语是更好的选择。

参考

  • 有效 Java,第二版
    • 项目 71:明智地使用惰性初始化

答案 2

下面是正确双重检查锁定的模式。

class Foo {

  private volatile HeavyWeight lazy;

  HeavyWeight getLazy() {
    HeavyWeight tmp = lazy; /* Minimize slow accesses to `volatile` member. */
    if (tmp == null) {
      synchronized (this) {
        tmp = lazy;
        if (tmp == null) 
          lazy = tmp = createHeavyWeightObject();
      }
    }
    return tmp;
  }

}

对于单例,有一个更具可读性的惰性初始化成语。

class Singleton {
  private static class Ref {
    static final Singleton instance = new Singleton();
  }
  public static Singleton get() {
    return Ref.instance;
  }
}

推荐