頭脳一式

人の記憶なんて曖昧なもの。すべての情報を頭に記憶するなんてナンセンス。困ったらここに来ればいいじゃん?というスタンスで最強のナレッジベースを目指すブログ

【GoFデザインパターン】Singletonパターン

Singletonパターン

世の中には「世界に1つだけしか存在しないもの(2つ以上は存在しないもの)」があります。
(例:地球、東京タワー、等)
オブジェクト指向の本質に鑑みれば「現実世界で1つだけ」のものは「JVM内の仮想世界でも1つだけであるべき」ということになります。
しかしクラスがある以上、newを行えば行った分だけインスタンスが生成されてしまうことになります。
これを防ぎ、絶対に1回しかnewできないように唯一無二のインスタンスを生成する手法をsingletonパターンといいます。
即ち、Singletonパターンでは、クラスのインスタンスが1つだけ存在することを保証するのです。

JavaでSingletonパターン

以下のコードはJavaで実装した例。

例①マルチスレッドを考慮してsynchronized を付与したパターン

public final class 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になっている理由】
「privateの場合、他のクラスからは利用できない」→「このコンストラクタは他クラスから呼びだせない。」→「自分自身でしかnewできない。」 ということ。

インスタンスを取得する仕組み】
一方、他クラスはgetInstance()メソッドを呼び出すことで、戻り値としてインスタンスが取得できる仕組みになっています。
何度getInstance()メソッドを呼び出したとしても、2回目以降は既にインスタンスがあるのでnewされず、1回目にnewしたときのインスタンスが返るようになっています。
またgetInstance()メソッドにsynchronizedを付与することによってマルチスレッドにも対応しています。(ステートレス)

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ブロックに処理が到達することはなくなります。