書きたいことを書きたいだけ。

とある会社員の生活記録。

デザインパターン「Singleton」

Singletonとは?

インスタンスが1つしか存在しないオブジェクトを生成するためのものです。
名前に「Single」がついているので分かりやすいかと思います。
クラス図を書いても、

f:id:itisyuu:20190513175611p:plain

と、しか書きようがありません。
ただ、1つしか存在しないインスタンスに何の価値があるかというと、逆に1つしか存在を許されないため価値があるのです。
つまりSingletonはインスタンスが1つしか無いことを保証します。
例えば、キャッシュやダイアログ、デバイスドライバなど2つ以上インスタンス化を行ったら何かしらの問題が起きそうな場合に有効です。
また、「1つしか存在しない」=「グローバル変数で最初に宣言」でもいいかもしれませんがグローバル変数の場合、プログラムが開始したときにオブジェクトを生成する必要があります。それに対してSingletonの場合は必要なときにオブジェクトを生成するので無駄がなくなります。

基本的な考え方

ただ1つのインスタンスを保証するクラスを作るためにどうしたらいいのかを考えます。
普段、クラスを書くときはコンストラクタに対して

public MyClass{
    public MyClass(){}
}

と大体は書きます。またこのクラスをインスタンス化する際には

new MyClass();

とします。
ではここで、コンストラクタの部分を

public MyClass{
    private MyClass(){}
}

としてみたらどうなるでしょう?つまり「public」から「private」にしています。
アクセス修飾子が「public」から「private」になることで、MyClassのコンストラクタはMyClass内からしか呼び出せなくなります。
そうなると、別のクラスから

new MyClass();

が出来なくなります。
このままではこのオブジェクトは永久にインスタンス化できないので何かしらインスタンス化できる方法を考えます。
アクセス修飾子がprivateはそのクラス内からしかアクセスできません。
そこで、

public MyClass{
    private MyClass(){}
    public static MyClass getInstance(){
        return new MyClass();
    }
}

とするとどうなるでしょう?getInstance()をpublicでしかもstaticでクラスメソッドにしてみました。
こうすると他のクラスからMyClassをインスタンス化する時はMyClass.getInstance()を呼び出すとインスタンスを取得できます。

インスタンスが1つだけ生成できるように工夫する

今までのことにちょっと工夫してインスタンスが1つだけになるようにしてみましょう。

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() {}
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

典型的なSingletonパターンの完成です。
インスタンス化を行いたい時には、Singleton.getInstance()を呼びます。
すると、if文で既にクラス変数のuniqueInstanceに生成されたSingletonが存在すればそれをreturnします。
逆にクラス変数がnullの場合だと新しいインスタンスを作成してクラス変数に保存しておきます。
つまり、インスタンスは一度しか生成されず2回目からは必ず以前に作成したインスタンスを取得できると言うことです。

典型的Singletonの改良

このままではマルチスレッド下において問題を発生してしまいます。
別々のスレッドでほぼ同時にgetInstance()を行った場合、お互いのスレッドはまだインスタンスが作成されていないと勘違いします。
そのまま処理を進めていくとインスタンスが2個出来上がる結果になります。
これはSingletonの定義でもある唯一のインスタンスを保証するということに反しますので修正する必要があります。
解決策としては色々あります。

  1. public static synchronized Singleton getInstance()とする ただしこの問題は初回にインスタンス化するときだけの問題なので、synchronizedは初回実行時以外オーバヘッドが大きい。

  2. 先にインスタンスを生成しておく private static Singleton uniqueInstance = new Singleton();
    とすることによって先に作るとこの問題は解決される。しかしクラスがロードされる時点でインスタンスを作成するので若干無駄。

  3. ダブルチェックロッキングを使う

public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton() {}
    public static  Singleton getInstance() {
        if (uniqueInstance==null) {
            synchronized (Singleton.class) {
                if(uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                    System.out.println("new instance");
                }
            }
        }
        return uniqueInstance;
    }
}

ダブルチェックロッキングを使うことによって、初回のみ正しく処理できるように保証を行うことができます。
ただし、同期化に失敗することも存在し現在ではあまり推奨されていない。

  1. ホルダーを作成する
final class Singleton {
  private Singleton() {}
  private static class SingletonHolder {
    private static final Singleton instance = new Singleton();
  }
  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

インスタンス化の部分を別のホルダーに隔離することによって保証を行う。
現在ではこのパターンが多いみたいです。

まとめ

Singletonパターンを使うことによってインスタンスが1つしか存在しないことを保証することができます。
ただし、同期化のコストを考えたりきちんと保証されているのかを考える必要があります。
また、SIngletonは一種のグローバル変数みたいなものです。スコープの範囲が広がりどこからでもアクセスすることが出来るため、注意が必要です。