第5章 Singleton - 増補改訂版Java言語で学ぶデザインパターン入門
第5章は、Singletonパターン。
今回のサンプルコードは必要最小限の長さなので、写経は楽勝。でもSingletonって内容は重い。GCとマルチスレッドを理解する必要がある。そして、私はそれ以前の問題であるクラスローダの仕組みを理解していなかったせいで(情けない…)、そっちを勉強するのにも時間がかかってしまった…。それはそうと、Singletonパターンの復習メモです。
Singletonを使う利点
生成されるインスタンスの数を、ただ一つに限定することができます。
Template MethodやFactory Methodと同じく、Singletonでもインスタンスを直接生成しません。クラスにpublicなメソッド getInstance() を用意します。Singletonパターンを使うクラスは、自分のインスタンスを private static なフィールドで保持しています。なので、getInstance()は、戻り値としてそのフィールド(変数)を返します。これでSingletonのただ一つのインスタンスを取り出すことができます。
唯一のインスタンスが生成されるタイミング
本文では、クラスの初期化の参考文献として『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 こういう些細なところに、習慣の良し悪し、発想の柔軟性や人としての親切さが出るような気がする…だめだめです。
たしか、結城さんの本『プログラマの数学』で、「除算の余りを上手く使えるようになりましょう」という話を読んだハズなのになぁ…。理解することと自然に使えることの間には、開きがありますね。