Singletonパターンを使ってクラスのインスタンスを1つにする(共有クラスのリソースを削減する方法)

マルチスレッド環境で共有リソースを使用するときに、各スレッドで同じオブジェクトを生成するとリソースの使用量が増えます。
またそのオブジェクトの初期化・生成にコストがかかるときには、同じ処理のために無駄なリソースを食うことになります。
このコストを削減するためには、インスタンスを使用する方法があります。

Singletonパターンを用いれば、構造上インスタンスを1つしか生成できないクラスを作成することができます。

Singletonパターンのポイントは、コンストラクタをprivateにして、インスタンスの取得に専用のstaticメソッドを用意することです。

Singletonパターンについては、デザインパターンをまとめたGang of Fourによる手法が有名です。

スポンサーリンク

呼び出し元のコード
注意点

Gang of Fourとは

Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人のことで、 デザインパターンをまとめた書籍『オブジェクト指向における再利用のためのデザインパターン』の著者です。

このコードは、マルチスレッド環境下でインスタンスが唯一であることを完全には保証できない、という問題が知られています。

public class GoFSingleton {

  private static GoFSingleton instance;

  private GoFSingleton() {
    // 最初はインスタンスを生成しない
    instance = null;
  }

  public static GoFSingleton getInstance() {
    // メソッド呼び出しがあったときに、初めてインスタンスを生成する
    if (instance == null) {
      // マルチスレッド環境下で、1つ目のスレッドがこの位置にいるときに、別のスレッドが上のif文の条件式を評価してしまうと、
      // 2つ以上のスレッドがこのブロック内に入り込めてしまう
      instance = new GoFSingleton();
    }
    return instance;
  }
  
  ……………
    ………
  ……………
}

getInstanceメソッドにsynchronized修飾子を付けることで、マルチスレッドの問題を回避することもできますが、メソッド呼び出しのたびに同期処理が行われるので、パフォーマンスが低下します。

同期処理のパフォーマンス低下を避けるために、次のような二重チェックをすることも考えられますが、この方法はJava仮想マシンの実装によっては、正しく動作しない問題が指摘されています。

public class GoFSingleton {

  private static GoFSingleton instance;

  private GoFSingleton() {
    // 最初はインスタンスを生成しない
    instance = null;
  }

  public static GoFSingleton getInstance() {
    if (instance == null) {
      synchronized(GoFSingleton.class) {
        // ここで再度チェックする
        if (instance == null) {
          instance = new GoFSingleton();
        }
      }
    }
    return instance;
  }
  
  ……………
    ………
  ……………
}

インスタンスの存在チェックをせず、インスタンスの初期化時にインスタンスを生成する方法もあります。
クラスがJava仮想マシンへロードされたときに、一度だけインスタンスが生成され、唯一のインスタンスとしてprivateなstatic変数として保持されます。
これ以後、インスタンスの生成は構造上不可能になります。

public class MySingleton {

  // このクラスに唯一のインスタンス
  private static MySingleton instance = new MySingleton();

  private MySingleton() {}

  // インスタンス取得メソッド
  public static MySingleton getInstance() {
    return instance;
  }
  
  ……………
    ………
  ……………
}

呼び出し元のコード

呼び出し側で、これらのインスタンスを取得するコードは次のようになります。

  public static void main(String[] args) {
    GoFSingleton goFSingleton = GoFSingleton.getInstance();

  }

ちなみにSingletonパターンではない場合は、次のようになると思いますので、ここを置き換えることになります。

  public static void main(String[] args) {
    GoFSingleton goFSingleton = new GoFSingleton();

  }

注意点

Singletonパターンは、容易に用いることができますが、使用には十分に注意する必要があります。
呼び出し元のクラス同士の依存関係が発生するからです。

次の条件を満たさない場合には使用するべきではないと思います。
クラスの初期化にコストがかかる場合
staticな値が、どの呼び出し元でも同じ値になる場合

関連記事

スポンサーリンク

>>= 演算子

ホームページ製作・web系アプリ系の製作案件募集中です。

上に戻る