シングルトンによるダブルチェックロッキング

シングルトンを使用したダブルチェックロック

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

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

これを修正するには、we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.

さらに、同期ブロックに入るとすぐに、同じチェックを再度実行して、操作をアトミックに保つ必要があります。

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つは、キャッシュのインコヒーレンスの問題を防ぐためにthe field needs to be volatileを使用することです。 実際、Javaメモリモデルでは、部分的に初期化されたオブジェクトを公開できます。これにより、微妙なバグが発生する可能性があります。

3. 代替案

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

  • 正しく機能するにはvolatileキーワードが必要なため、Java1.4以下のバージョンとは互換性がありません。

  • 非常に冗長で、コードが読みにくくなります

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

3.1. 初期の初期化

スレッドセーフを実現する最も簡単な方法は、オブジェクトの作成をインライン化するか、同等の静的ブロックを使用することです。 これは、静的フィールドとブロックが次々に初期化されるという事実を利用しています(Java Language Specification 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の本(アイテム3)からのものであり、classの代わりにenumを使用します。 執筆時点では、これはシングルトンを記述するための最も簡潔で安全な方法と考えられています。

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. 結論

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

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

いつものように、すべての例のコードはavailable on GitHubです。