第5章 Singleton - 増補改訂版Java言語で学ぶデザインパターン入門

第5章は、Singletonパターン。

今回のサンプルコードは必要最小限の長さなので、写経は楽勝。でもSingletonって内容は重い。GCとマルチスレッドを理解する必要がある。そして、私はそれ以前の問題であるクラスローダの仕組みを理解していなかったせいで(情けない…)、そっちを勉強するのにも時間がかかってしまった…。それはそうと、Singletonパターンの復習メモです。

Singletonを使う利点

生成されるインスタンスの数を、ただ一つに限定することができます。

Template MethodやFactory Methodと同じく、Singletonでもインスタンスを直接生成しません。クラスにpublicなメソッド getInstance() を用意します。Singletonパターンを使うクラスは、自分のインスタンスを private static なフィールドで保持しています。なので、getInstance()は、戻り値としてそのフィールド(変数)を返します。これでSingletonのただ一つのインスタンスを取り出すことができます。

クラス外からインスタンスを直接生成されないように、コンストラクタをprivateにしておくことがポイントです。

唯一のインスタンスが生成されるタイミング

本文では、クラスの初期化の参考文献として『Java言語仕様 第3版 (The Java Series)』が紹介されています。そんな難しい本、もってないよー。というわけで、Webで確認。

クラスがロードされて初期化された時に、staticフィールドも初期化されます。Singletonクラスのロード、つまり、クライアントのコードからgetInstance()が呼び出された時に、Singletonのただ一つのインスタンスは生成されます。

複数のSingletonインスタンスができないよう気をつける

問題5-3。singletonが遅れてインスタンス化される場合は、スレッドセーフにしましょうという話。

    // 危ないサンプル。synchronizedが必要。
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

Javaの格言―より良いオブジェクト設計のためのパターンと定石』のp.224で、ほぼ同じコードを使ってこの問題が解説されています。singletonインスタンスのnullチェックからSingletonのコンストラクタ呼出しまでのわずかな時間に、他のスレッドから (singleton == null) が再評価されると、条件文の値がtrueになってしまいます。すなわち、Singletonのインスタンスが、さらに1つ生成されます。

解決策は、getInstance()をスレッドセーフにすることです。

孤立したSingletonがGCの対象にならないよう気をつける

こっちは本の内容から逸れるけど、大事なので復習。Singletonパターンで生成された唯一のインスタンスは、孤立したとき、GCの対象となる可能性があります。この解決策として、前述の『Javaの格言』p.p.208-209で2つの方法が挙げられています。

  • プログラムの起動時に-Xnoclassgcを使って、パーマネント領域全体をGCの対象外とする
  • Singletonクラス群の参照を保持し続けるための専用のクラスを作る

GCの恩恵を他の場所で受けられるからという理由で、2つ目の方法が推奨されるようです。

恥をさらす余談

本文のサンプルコードを淡々と写経しつつ、練習問題にも一応取り組んでいるわけですが…。

インスタンスの個数が3個に限定されるクラスを作る。インスタンスには0、1、2の番号がついていて、getInstance(int id)で、id番のインスタンスを得られる」という問題5-2。

結城さんの回答は、クライアントのコードで整数を3で割り、余りをgetInstanceの引数として渡している。つまり、必ず引数が0〜2になるように、クライアント側で調整している。それに比べて、自分が書いたのは、0〜2以外の引数でgetInstanceを使おうとしたらIllegalArgumentExceptionを投げるという酷い仕様w こういう些細なところに、習慣の良し悪し、発想の柔軟性や人としての親切さが出るような気がする…だめだめです。

たしか、結城さんの本『プログラマの数学』で、「除算の余りを上手く使えるようになりましょう」という話を読んだハズなのになぁ…。理解することと自然に使えることの間には、開きがありますね。