【オブジェクト指向のこころ】21章 Singleton, Double-Checked Locking, Initialization-on-demand holder idiom

デザインパターンとともに学ぶオブジェクト指向のこころ」の学習記録。
「21章 SingletonパターンとDouble-Checked Lockingパターン」のまとめ。

  • Singletonパターン: シングルスレッドアプリケーションで使用される
  • Double-Checked Lockingパターン: マルチスレッドアプリケーションで使用される

Singletonパターン

Singletonパターンの本質は、アプリケーション中の全オブジェクトがSingletonの同一インスタンスを使うようになる、という点。
誰かが責任を持ち、インスタンスを使用するオブジェクトに対して、いちいちインスタンスを引き渡すようなことはすべきではない。
どこかのオブジェクトで該当インスタンスに加えた変更を、他オブジェクトからも参照できるようにする。

Singletonパターンによって、オブジェクト自体に自らの実体化を一度だけにする責務を持たせることが可能になる。
またクライアント側はそのオブジェクトの存在有無を気にすることなく使用できるようになる。

Singletonパターンをマルチスレッドで利用したときの問題

マルチスレッドアプリケーションでは、Singletonパターンを使うことによって問題が引き起こされる場合がある。

  1. 最初のオブジェクトがインスタンスの存在をチェックし存在しないため、インスタンスの生成コードが実行されようとする。
  2. この生成が完了する前に、別のスレッドがインスタンスの存在をチェック、つまりメンバがnullかどうか確認したとする。
    最初のスレッドによってまだインスタンスが生成されていない場合は該当メンバはnullのため生成コードが実行されようとする。
  3. 双方のスレッドがSingletonオブジェクトの生成コードを実行することにより、2つのオブジェクトが生成されてしまう。

これにより問題が発生するかどうかはケースによる。

  • Singletonに状態が保持されていない場合は問題にならないこともある。
  • Singletonに状態が保持されている場合、最初に実体化を行ったスレッドが参照するオブジェクトと、それ以外のスレッドが参照するオブジェクトは別のものになってしまう。
    これによりスレッド間で状態の整合性が取れなくなったり、このオブジェクトがコネクションを確立する場合は2つのコネクションが確立されてしまう。

たった一つのクライアントだけが異なったSingletonオブジェクトを保持することになり、原因究明は難しいものとなる。

Double-Checked Lockingパターン

対策として、Singletonオブジェクトがすでに存在しているかどうかチェックしている部分を同期化すればよさそうだが、これは全てのスレッドがそのチェックのために待たされることになるためパフォーマンス低下の一因となる。

別の案として、if(instance == null) という条件分岐内に同期化を入れてみる手が考えられる。
これでも2つの呼び出しがほぼ同時にnull条件をクリアした場合、2つのスレッドは順番にオブジェクトを生成してしまう。

そこで、nullチェックの後に同期を行い、同期ブロック内で再度インスタンスメンバが生成されていないかどうかを確認する。
これで不必要な同期が行われないようにできる。
同期化部分のコードはを実行するのは、オブジェクトの生成が完了していないタイミングでメソッドの実行を開始したスレッドだけとなる。
この手法がDouble-Checked Lockingパターンである。

Double-Checked LockingパターンはJavaでは使用できない

しかし、Javaではコンパイラの最適化機能によって、コンストラクタによるオブジェクトの実体化作業が完了する前に、該当オブジェクトの参照が返される場合があり、うまく動作しないことが分かっている。
Double-Checked LockingパターンはJavaでは使用してはいけない。

Initialization-on-demand holder idiom

この問題は、Initialization-on-demand holder idiomという手法を利用することで解決できる。

public class Something {
    private Something() {}

    private static class LazyHolder {
        static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

en.wikipedia.org