連載
» 2004年08月10日 10時00分 UPDATE

JavaTips 〜Javaプログラミング編:クラスのインスタンスを1つに保つ(Singletonパターン)

[佐藤匡剛,@IT]

 サーブレットのようなマルチスレッド環境下で共有リソースを扱うとき、複数のスレッド間で単一のオブジェクトを操作するようなプログラムが必要になることがあります。また、マルチスレッド環境以外でも、プログラム全体の情報を共有するオブジェクトや、生成に非常にコストが掛かるものの使い回しが利くオブジェクトのように、プログラム全体で1つのインスタンスだけを扱いたいこともあります。

 小規模なプログラムなら、インスタンスを1つしか作らないという暗黙の規則を作り、注意深くコーディングすれば対応することはできるでしょう。しかし、プログラムの規模が大きくなってくると、そのような暗黙の規則だけではいずれ破たんしてしまいます。もともと1つだけのインスタンスを扱うように作られたクラスが、いつの間にか複数のインスタンスで扱われていたりすると、バグを引き起こす原因になります。

 このような問題の解決には、クラスのstatic変数を利用して状態を共有するという方法があります。しかし、C/C++言語におけるグローバル変数に相当するstatic変数を多用してしまうと、クラス間の依存関係が強くなったり、動作を追跡するのが難しいコードにしてしまう原因となります。

 このようなときは、Singletonパターンというデザインパターンを利用することをお勧めします。Singletonパターンを用いれば、構造上インスタンスを1つしか生成できないクラスを作成することができます。

MySingleton.java
package javatips;

public class MySingleton {

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

  private MySingleton() {}

  // インスタンス取得メソッド
  public static MySingleton getInstance() {
    return instance;
  }
  
  // 以後、通常のフィールドやメソッドの宣言
                  ...中略...
}


 Singletonパターンのポイントは、「コンストラクタをprivateにしてしまう」ことです。そして、唯一のインスタンスをprivateなstatic変数として保持しておくのです。こうすることで、そのクラスがJava仮想マシンへロードされたときに、一度だけインスタンスが生成されます。そして、これ以後、インスタンスの生成は構造上不可能になります。インスタンスの取得には、専用のstaticメソッドを用意します。

 すでにGang of Four(以下GoF)(注)によるオリジナルのSingletonパターンをご存じの方は、ここで示した実装が彼らの示した実装と少し異なっていることに気付かれたかもしれません。オリジナルに近いコードは、以下のようになります。しかし、こちらのコードは、マルチスレッド環境下でインスタンスが唯一であることを完全には保証できない、という問題が知られています。

注:Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人を指します。デザインパターンをまとめた最初の書籍『オブジェクト指向における再利用のためのデザインパターン』の著者であり、彼らによって、このSingletonパターンを含む23個のデザインパターンが紹介されました。

GoFSingleton.java
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修飾子を付けることで、マルチスレッドの問題を回避することもできますが、メソッド呼び出しのたびに同期処理が行われるので、パフォーマンスが低下してしまいます。

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

GoFSingleton.javaに二重チェックを追加(抜粋)
  public static GoFSingleton getInstance() {
    if (instance == null) {
      synchronized(GoFSingleton.class) {
        // ここで再度チェックするため、「二重チェック」と呼ばれる
        if (instance == null) {
          instance = new GoFSingleton();
        }
      }
    }
    return instance;
  }


 従って、最初に示したMySingleton.javaの実装方法を採用するのが、最も無難です。

 MySingletonクラスのインスタンスを取得するコードは、以下のようになります。ここでは、2つのインスタンスを取得して、Object#equals(Object)メソッドを用いて両者の同一性をチェックしています。

  public static void main(String[] args) {
    MySingleton instance1 = MySingleton.getInstance();
    MySingleton instance2 = MySingleton.getInstance();
    System.out.println(instance1.equals(instance2));
  }


 このコードの実行結果は以下のようになります。

> javac javatips\MySingleton.java
> java javatips.MySingleton
true


 Singletonパターンは、デザインパターンの中では比較的簡単に理解でき、利用も容易です。そのため、多用される傾向にあるのですが、static変数の多用と同様で、Singletonパターンの多用も好ましくありません。グローバル変数ほどではないにせよ、クラス間の依存関係を不必要に強めることになるからです。

 そのため、Singletonパターンの利用は、どうしてもインスタンスを1つに制限しなくてはならない場合だけに限定することをお勧めします。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。