双重检查锁定 (DCL) 及其修复方法

Double-checked Locking (DCL) and how to fix it

我正在阅读有关双重检查锁定的 article。讨论的问题如下:

public class MyFactory {
    private static MyFactory instance;

    public static synchronized MyFactory getInstance() {
        if (instance == null)
            instance = new MyFactory();
        return instance;
    }

    private MyFactory() {}
}

如何避免每次 getInstance() 调用同步的建议方法之一是使用 class 加载器:

public class MyFactory {
    private static final MyFactory instance;

    static {
        try {
            instance = new MyFactory();
        } catch (IOException e) {
            throw new RuntimeException("Darn, an error's occurred!", e);
        }
    }

    public static MyFactory getInstance() {
        return instance;
    }

    private MyFactory() throws IOException {
        // read configuration files...
    }
}

作者说:“... JVM 确保在首次引用和加载 class 时只调用此静态初始化代码一次。”我完全同意。但是我不明白以下内容:"Using the class loader is generally my preferred way of dealing with lazy static initialisation."

第二个代码片段是惰性静态初始化吗?

不是偷懒。这是懒惰的(使用 class 加载程序):

public class Singleton {

    public static class SingletonHolder {
        public static final Singleton HOLDER_INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.HOLDER_INSTANCE;
    }
}

玩双重检查锁就像玩火。出错的方式有很多种。

Double-checked locking idiom 的目的是为了避免不必要的synchronization。因此,您的第一个代码片段不属于该类别。

来到你的第二个代码片段,如果你正在创建的单例是static,那么有一个紧凑的解决方案,你可以将单例定义为[=中的static字段33=]分开class而不在同一个class.

class MyFactorySingleton {
  static MyFactory singleton = new MyFactory();
} 

Java 的这种语义保证在引用该字段之前不会初始化该字段,并且访问该字段的任何线程都将看到初始化该字段所产生的所有写入。

但是从 JDK5 及更高版本开始,Java 已经开始支持 volatile 语义,即

the value of a variable which is declared volatile will never be cached thread-locally. All reads and writes will go straight to "main memory". Access to the variable acts as though it is enclosed in a synchronized block, synchronized on itself.

因此,双重检查外观的一些万无一失的设计应该如下所示:

class MyFactory {
  private volatile MyFactory instance = null;

  public MyFactory getInstance() {
     if(instance == null) {
        synchronized(MyFactory.class) {
           if(instance == null) {
              instance = new MyFactory();
           }
        }
     }
     return instance;
  }
}

教授。 Joshua Bloch 和他的合著者解释了双重检查锁定在这个 article 中是如何出错的。值得一读。

不完全是。初始化发生在加载 MyFactory class 时,而不是调用 getInstance() 时。如果你想在调用方法时初始化实例,你可以使用一个holder class.

public class MyFactory {
    private static final class Holder {
        private static final MyFactory instance = new MyFactory();
    }

    public static MyFactory getInstance() {
        return Holder.instance;
    }
}