1. 序章

このチュートリアルでは、ダブルチェックされたロックのデザインパターンについて説明します。 このパターンは、事前にロック状態をチェックするだけで、ロック取得の回数を減らすことができます。 この結果、通常、パフォーマンスが向上します。

それがどのように機能するかを詳しく見てみましょう。

2. 実装

まず、厳格な同期を備えた単純なシングルトンについて考えてみましょう。

public class DraconianSingleton {
    private static DraconianSingleton instance;
    public static synchronized DraconianSingleton getInstance() {
        if (instance == null) {
            instance = new DraconianSingleton();
        }
        return instance;
    }

    // private constructor and other methods ...
}

このクラスはスレッドセーフですが、明らかにパフォーマンス上の欠点があることがわかります。シングルトンのインスタンスを取得するたびに、潜在的に不要なロックを取得する必要があります。

それを修正するには、 代わりに、最初にオブジェクトを作成する必要があるかどうかを確認することから始めることができ、その場合にのみロックを取得します。

さらに、操作をアトミックに保つために、同期ブロックに入るとすぐに同じチェックを再度実行したいと思います。

public class DclSingleton {
    private static volatile DclSingleton instance;
    public static DclSingleton getInstance() {
        if (instance == null) {
            synchronized (DclSingleton .class) {
                if (instance == null) {
                    instance = new DclSingleton();
                }
            }
        }
        return instance;
    }

    // private constructor and other methods...
}

このパターンで覚えておくべきことの1つは、キャッシュのインコヒーレンスの問題を防ぐために、フィールドを揮発性にする必要があるということです。 実際、Javaメモリモデルでは、部分的に初期化されたオブジェクトの公開が可能であり、これにより、微妙なバグが発生する可能性があります。

3. 代替案

ダブルチェックされたロックは潜在的に物事をスピードアップすることができますが、少なくとも2つの問題があります。

  • volatile キーワードが正しく機能する必要があるため、Java1.4以前のバージョンとは互換性がありません
  • それは非常に冗長であり、コードを読みにくくします

これらの理由から、これらの欠陥のない他のいくつかのオプションを調べてみましょう。 次のすべてのメソッドは、同期タスクをJVMに委任します。

3.1. 早期初期化

スレッドセーフを実現する最も簡単な方法は、オブジェクトの作成をインライン化するか、同等の静的ブロックを使用することです。 これは、静的フィールドとブロックが次々に初期化されるという事実を利用しています(Java言語仕様12.4.2):

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }
    
     // private constructor and other methods...
}

3.2. オンデマンドでの初期化

さらに、前の段落のJava言語仕様リファレンスから、クラスの初期化はそのメソッドまたはフィールドの1つを初めて使用するときに発生することがわかっているため、ネストされた静的クラスを使用して遅延初期化を実装できます。

public class InitOnDemandSingleton {
    private static class InstanceHolder {
        private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
    }
    public static InitOnDemandSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }

     // private constructor and other methods...
}

この場合、 InstanceHolder クラスは、getInstance。を呼び出して最初にアクセスしたときにフィールドを割り当てます。

3.3. 列挙型シングルトン

最後の解決策は、JoshuaBlockによるEffective Java ブック(アイテム3)からのものであり、クラスの代わりに列挙型を使用します。 これを書いている時点では、これがシングルトンを書くための最も簡潔で安全な方法であると考えられています。

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. 結論

要約すると、この簡単な記事では、再確認されたロックパターン、その制限、およびいくつかの代替案について説明しました。

実際には、過度の冗長性と下位互換性の欠如により、このパターンはエラーが発生しやすくなるため、回避する必要があります。 代わりに、JVMに同期を実行させる代替手段を使用する必要があります。

いつものように、すべての例のコードはGitHubで入手できます。