デザインパターン - Singleton
このクラス図で注目すべきことは以下の3点である。
- 同じ型のインスタンスが private なクラス変数として定義されている。
- コンストラクタの可視性が private である。
- 同じ型のインスタンスを返す
getInstance()
がクラス関数として定義されている。
クラス図内にあるアンダーラインは、その項目がクラス変数あるいはクラス関数であることを意味している。
Javaでの実装例[編集]
以下にSingleton パターンを用いたクラスのJavaによる例を示す。
final class Singleton {
private static Singleton instance;
private Singleton(){};
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
このクラスにおいて、コンストラクタはprivate
で定義されているため、他のクラスによって、Singleton
クラスのインスタンスを生成することはできない。このクラスのインスタンスを生成したいときは、getInstance()
メソッドを利用することになるが、このメソッドは最初に呼び出されたときにだけインスタンスを生成し、2回目以降に呼び出されたときは最初に生成したインスタンスを返すように作られている。そのため、プログラム中にSingleton
クラスのインスタンスが1つしか存在しないことが保証される。
getInstance()
メソッドがsynchronized
に指定されているのは、複数のスレッドからほぼ同時に呼び出された際に複数のインスタンスが生成されてしまう危険性をなくすためである。
問題点および改善策[編集]
Java における一般的な Singleton パターンの記述は上述した通りであるが、この方法は同期化コストが高い。そこでdouble-checked lockingというイディオム [2]が考えられたが、「アウトオブオーダー書き込み」を許すJavaプラットフォームのメモリー・モデルにより同期化に失敗する(言うまでもないが、この最適化による副作用は Java だけのものではない)。この問題を克服しようとするとコードが肥大化しコストがかかる。そこで現在では以下のように static フィールドを用いることが推奨されている。
final class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return Singleton.instance;
}
}
これによりコストは改善される。同期化は行われないが、static フィールドの初期化はそのクラスが呼び出される最初の一回しか行われないため、何回getInstance()
メソッドを呼んでもスレッドアンセーフを心配する必要はなくなるだけでなく、コストパフォーマンスも非常に高い。
ただしこの場合、Singletonクラスがロードされたときに初期化されるのであって、getInstance()
が初めて呼ばれたときではない。このことはプログラマーの意図しないタイミングで初期化が始まってしまい混乱の元となる場合がある。 そこで en:Initialization-on-demand holder idiom と呼ばれる手法、すなわちinstanceフィールドのみを別のホルダークラスに隔離して、そのホルダークラスがロードされたときにSingletonの初期化がおこわれるよう改善したものが下記である。
final class Singleton {
private Singleton(){}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
これでSingletonクラスがロードされたときではなく、getInstance()
呼び出しによりSingletonHolderクラスがロードされたときにSingletonクラスが初期化される。