二重チェックイディオム

以前、Javaではダブルチェックイディオムを使ってはいけないと言われていた。Effective Java第1版にもダメと書かれていたのだが、Effective Java 読書会 12 日目 「スレッド・セーフってなによ!!」 - IT戦記によると、第2版では遅延初期化の方法として掲載されているらしい。

ちょっと調べてみた。

JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write.

If you need high-performance lazy initializing of an instance field, use the double-check idiom with a volatile field. This idiom wasn't guaranteed to work until release 5.0, when the platform got a new memory model. The idiom is very fast but also complicated and delicate, so don't be tempted to modify it in any way. Just copy and paste -- normally not a good idea, but appropriate here:

な、なんだってー!!Java1.5からメモリモデルが修正されたのでvolatileと一緒に使えば問題なくなったのか…。マジかよ知らんかった。

2重チェックイディオムはインスタンスフィールドに対する最速の遅延初期化用のイディオムと書かれている。staticフィールドの初期化、例えばシングルトンオブジェクトの遅延初期化はどうするのか?

If you need better performance, your best choice depends on whether you're initializing a static field or an instance field. If it's a static field, use the lazy initialization holder class idiom:

staticフィールドの高速な遅延初期化には lazy initialization holder class idiom を使えとのこと。何それ?

public class Something {
    private Something() {
    }
 
    private static class LazyHolder {
        private static final Something something = new Something();
    }
 
    public static Something getInstance() {
        return LazyHolder.something;
    }
}

これは、Initialization on demand holder idiom とも呼ばれているらしい。staticなメンバクラスのフィールドにシングルトンオブジェクトを保持する。クラスは必要とされるまで初期化されず、かつクラスの初期化はシリアルに処理されることが保障されているので、getInstance メソッドを synchronized しなくても確実に一度だけ初期化されたオブジェクトを取得できるようだ。

これも第2版に掲載されている内容のようだ。やはり第1版持ってても買う価値は十分にある。というわけで今更ながら注文した。