Javaでシングルトンの遅延初期化

もっとも簡単なシングルトンの例

@ThreadSafe
final class Singleton {
  public static final Singleton INSTANCE = new Singleton();
  private Singleton() {}; 
}

staticフィールドの初期化は、そのクラスに初めてアクセスされた(≠クラスがロードされた)時に行なわれる。

そこで次の例では、インスタンスが必要になる時まで生成を遅らせている。

@ThreadSafe
final class Singleton {
  private static Singleton instance;
  private Singleton() {}; 
  public static synchronized Singleton getInstance() {
    if(instance == null) { 
      instance = new Singleton();
    }
    return instance;
  } 
}

確かにインスタンスの生成は遅らせされたが、getInstanceを実行する度にスレッドの同期が行なわれるのはうれしくない。

そこで登場するのが悪名高きdouble-checked locking pattern。
アウトオブオーダー書き込みのせいで、不完全なインスタンスが別スレッドに公開されることがあるので使ってはいけない。

@NotThreadSafe
final class Singleton {
  private static Singleton instance;
  private Singleton() {}; 
  public static Singleton getInstance() {
    if(instance == null) { 
      synchronized(Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

Java 5.0からは変数にvolatile修飾子を付けることでこれを防げる。
以下はJava 5.0以降でのみ安全なdouble-checked locking pattern。

@ThreadSafe
final class Singleton {
  private static volatile Singleton instance;
  private Singleton() {}; 
  public static Singleton getInstance() {
    if(instance == null) { 
      synchronized(Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

そして、最後に示すのがInitialization-on-demand holder idiom。
LazyHolderの初期化は、getInstance()が実行されるまで行なわれないので、
スレッドセーフかつ高速なシングルトンの遅延初期化が実現できる。

@ThreadSafe
public class Singleton {
  private Singleton() {}
 
  private static class LazyHolder {
    public static final Singleton INSTANCE = new Singleton();
  }
 
  public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
  }
}

refs