Singletonパターンとは
世の中には「世界に1つだけしか存在しないもの(2つ以上は存在しないもの)」があります。
(例:地球、東京タワー、等)
オブジェクト指向の本質に鑑みれば「現実世界で1つだけ」のものは「JVM内の仮想世界でも1つだけであるべき」ということになります。
しかしクラスがある以上、newを行えば行った分だけインスタンスが生成されてしまうことになります。
これを防ぎ、絶対に1回しかnewできないように唯一無二のインスタンスを生成する手法をsingletonパターンといいます。
即ち、Singletonパターンでは、クラスのインスタンスが1つだけ存在することを保証するのです。
JavaでSingletonパターン
以下のコードはJavaで実装した例。
例①マルチスレッドを考慮してsynchronized を付与したパターン
public final singleton {
private static singleton theInstance; //インスタンス保持用変数
private singleton(){} //プライベートコンストラクタ
public static synchronized singleton getInstance(){ //インスタンス取得用メソッド
if (theInstance == null){
theInstance = new singleton();
}
return theInstance;
}
}
↑のSingletonを操作する側の記述例が以下
singleton obj = new singleton(); //この記述はコンストラクタが不可視でコンパイルエラー。
singleton obj1 = singleton.getInstance(); //この記述でインスタンスを取得できる。
解説
「privateの場合、他のクラスからは利用できない」→「このコンストラクタは他クラスから呼びだせない。」→「自分自身でしかnewできない。」
ということになります。
一方、他クラスはgetInstance()
メソッドを呼び出すことで、戻り値としてインスタンスが取得できる仕組みになっています。
何度getInstance()
メソッドを呼び出したとしても、2回目以降は既にインスタンスがあるのでnewされず、1回目にnewしたときのインスタンスが返るようになっています。
またgetInstance()
メソッドにsynchronized
を付与することによってマルチスレッドにも対応しています。(ステートレス)
前述のとおりsynchronized
を付与するのはマルチスレッドに対応するためですが、もしこれを付与しなかった場合は以下のような問題を抱えることになります。
例えば2つ以上のスレッドがgetInstance()
メソッドを通じてインスタンスを取得しようとした場合、
1つ目のスレッドでif(theInstance == null)
がTrueとなりtheInstance = new singleton()
を実行している間に、
2つ目のスレッドがgetInstance()
メソッドに入ってきてインスタンスを生成してしまう。
例②ダブルチェックを採用したパターン
例①には問題点があります。それは複数のスレッドがgetInstance()
メソッドを実行するたびに同期化が行われ性能が劣化するということです。
これを解決するための手法としてダブルチェックというものがあります。
その実装が以下です。
public class singleton2 {
private static singleton2 theInstance; //インスタンス保持用変数
private singleton2(){} //プライベートコンストラクタ
public static singleton2 getInstance(){ //インスタンス取得用メソッド
if (theInstance == null){
synchronized (singleton2.class){
if (theInstance == null){ //ここでダブルチェック。
theInstance = new singleton2();
}
}
}
return theInstance;
}
}
解説
この手法により性能改善が見込まれます。
なぜかというと、複数のスレッドがgetInstance()
メソッドに入っただけでは同期化が行われないからです。
インスタンスがnull(インスタンス化される前)の場合にのみ同期化が行われるため、
一度インスタンスを生成してしまえばsynchronized
ブロックに処理が到達することはなくなります。