シングルトンと遅延初期化

シングルトンなクラスを定義するために以下のように記述されたコードをよく目にする。

class Singleton {
  private volatile static Singleton instance;

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null) {
          instance = new Singleton(); //(1)
        }
      }
    }
    return instance;
  }

これは、1度しかinstanceを生成できないことを保証して、かつ最小限の同期処理でシングルトンを実現するための「二重チェック」イディオムを使ったコードなんだけど、このコードはjava1.4以前では正しく機能しない。(1)の箇所で「アウトオブオーダー書き込み」と呼ばれる現象が発生して、別のスレッドが不完全な状態のinstanceを参照してしまう危険性があるため。このあたりは以下の文章が詳しい。

double-checked lockingとSingletonパターン
http://www.ibm.com/developerworks/jp/java/library/j-dcl/

ただし、Java5以降ではvolatileキーワードの機能が強化されているため、上記のコードは正しく動作するようになっている。Effective Javaにも以下のように書かれている。

今日では、二重チェックイディオムは、インスタンスフィールドの初期化を遅延して行うための最適の技法です。
Effective Java 第2版 の274ページ

いろいろややこしいが、これはシングルトンがどうのこうのという話ではなく、遅延初期化のテクニックに関する話だな。
単にシングルトンなクラスを書きたいときは、以下のように書けば、ほとんどの場合には事足りるであろう。

class Singleton {
  private static final Singleton instance = new Singleton();
  public static Singleton getInstance() {
    return instance;
  }

自分はほとんどの場合、上のような感じでシングルトンを実装することが多かったので、二重チェックの問題について深く考えることはあまり無かった。

ということで、シングルトンについて認識を改めるきっかけをくれた、今日の社内LTの発表者さんと、twitterでいろいろアドバイスをいただいたppoiさん、t_yanoさん、oroshisobaさんに感謝!