シングルトンパターンとは
シングルトンパターンは、GoFによって定義されたデザインパターンの一つで、特定のクラスの
インスタンスが一つしか生成されないことを保証するものです。アプリケーション全体で一意に保つ必要のある設定やリソース管理などに利用されます。例えば、ロケール設定やルックアンドフィールなどを一元管理する際に有効です。
シングルトンパターンの
クラス図は以下の特徴を持ちます。
同じ型のインスタンスを保持するprivateなクラス変数
privateなコンストラクタ
インスタンスを返すクラス関数getInstance()
クラス図内のアンダーラインは、クラス変数またはクラス関数であることを示します。
Javaでの実装例を見てみましょう。
java
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
この例では、コンストラクタを`private`にすることで外部からのインスタンス生成を防ぎます。`getInstance()`メソッドは、最初に呼ばれた時にのみインスタンスを生成し、それ以降は既存のインスタンスを返します。`synchronized`キーワードは、マルチスレッド環境で複数のインスタンスが生成されるのを防ぐために使用されます。
問題点と改善策
同期化コスト
`synchronized`による同期化はコストが高いため、double-checked lockingという手法が試みられましたが、Javaのメモリモデルの問題で失敗する可能性がありました。この問題を解決するために、staticフィールドを使った以下のような方法が推奨されています。
java
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
この方法では、クラスロード時にインスタンスが生成されるため、同期化の必要がなくなり、効率が向上します。ただし、クラスがロードされた時点でインスタンスが初期化されるため、getInstance()メソッドが呼ばれるまでインスタンスの生成を遅延させたい場合には問題があります。
Initialization-on-demand holder idiom
インスタンスの遅延初期化を実現するために、Initialization-on-demand holder idiomが利用されます。この手法では、内部クラスを使ってインスタンスを保持します。
java
public class Singleton {
private Singleton() { }
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
`SingletonHolder`クラスがロードされる際にインスタンスが生成されるため、getInstance()が呼ばれるまでインスタンスの生成が遅延されます。
環境ごとの注意点
クラスのアンロード
Javaでは、参照されなくなったクラスがアンロードされることがあり、staticフィールドも無効になります。再度クラスがロードされるとインスタンスが再生成されてしまい、シングルトンが破綻する可能性があります。特にAndroid環境では、アプリがサスペンドされるとクラスがアンロードされやすいため、注意が必要です。Androidでシングルトンを実現する場合は、`android.app.Application`を継承したクラスを利用するなど、フレームワークが提供する機構を使うことを検討する必要があります。
Java EE 6以降では、`@Singleton`アノテーションを使うことで、より簡単にシングルトンを実装できます。
その他の言語での実装例
PHP
PHP 5.0以降では、Javaに近い形でシングルトンパターンを実装できます。基本的な構造はJavaの実装例とほぼ同様です。
C++では、コンストラクタ、コピーコンストラクタ、コピー代入演算子を`private`にすることでシングルトンを実現します。静的ローカル変数を利用した実装例は以下の通りです。
cpp
class Singleton {
private:
Singleton() {}
Singleton(const Singleton& other) = delete;
Singleton& operator=(const Singleton& other) = delete;
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
~Singleton() = default;
};
ただし、C++11以前の規格では、静的ローカル変数の初期化がスレッドセーフではないため、注意が必要です。C++11以降では、静的ローカル変数の初期化はスレッドセーフになります。
特徴
シングルトンパターンは、グローバル変数のように振る舞うことができます。コード中のどこからでも同じインスタンスにアクセスできるため、利便性が高い一方で、グローバル変数と同様に、依存関係が不明確になるなどの問題を引き起こす可能性があります。また、インスタンスの生成回数を制御できるという利点もあります。例えば、特定の回数までは新しいインスタンスを生成し、それ以降は既存のインスタンスを再利用するようにすることも可能です。
Multitonパターン
シングルトンパターンを拡張したMultitonパターンは、複数のインスタンスを管理する際に利用されます。ただし、Multitonパターンは、ユニットテストを難しくする可能性や、ガーベジコレクションのある環境ではメモリリークを引き起こす可能性がある点に注意が必要です。
まとめ
シングルトンパターンは、アプリケーション全体で一意に管理したいリソースを扱う場合に有効なデザインパターンです。しかし、実装方法や環境によって注意すべき点もあります。メリットとデメリットを理解した上で、適切に利用することが重要です。
関連項目
デザインパターン
クリティカルセクション
排他制御
マルチスレッド
グローバル変数