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 ...
}

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

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

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

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つは、キャッシュの整合性の問題を防ぐために

このフィールドは

volatile__ ** である必要があるということです。実際、Javaメモリモデルでは、部分的に初期化されたオブジェクトの公開が許可されているため、これがさらに微妙なバグにつながる可能性があります。


3代替案

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

  • 正しく機能するには

    volatile

    キーワードが必要なので、そうではありません。

Java 1.4以前のバージョンとの互換性
** これは非常に冗長であり、コードを読みにくくします

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


3.1. 早期初期化

スレッドセーフを実現する最も簡単な方法は、オブジェクトの作成をインライン化するか、同等の静的ブロックを使用することです。これは、静的フィールドとブロックが次々に初期化されるという事実を利用したものです(https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2言語仕様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. 列挙型シングルトン

最後の解決策はJoshua Blockによる

Effective Java

book(Item 3)から来て、

class

の代わりに

enum

を使用します。これを書いている時点では、これはシングルトンを書くための最も簡潔で安全な方法であると考えられています。

public enum EnumSingleton {
    INSTANCE;

   //other methods...
}


4結論

まとめると、この簡単な記事では、二重チェックによるロックパターン、その限界、およびいくつかの選択肢について説明しました。

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

いつものように、すべての例のコードはhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns[GitHubで入手可能]です。