1. シングルトンパターンとは
シングルトンパターンは、クラスのインスタンスがシステム全体で1つだけ存在することを保証し、そのインスタンスへのグローバルなアクセス方法を提供します。例えば、組織内に校長が1人だけいる状況に似ています。このパターンは、オブジェクト生成を制御する最も基本的な手法の1つです。
2. シングルトンを使用する利点
- メモリ内にインスタンスが1つしか存在しないため、頻繁な生成と破棄のオーバーヘッドを回避し、システムリソースを節約します。
- グローバルなアクセスポイントを提供することで、共有リソースへのアクセスを効率的に管理できます。
3. 代表的な実装パターン
3.1 即時初期化(Eager Initialization)
この方式はスレッドセーフで呼び出し効率が高いですが、遅延ロードはできません。
public class SingletonEager {
// コンストラクタをprivateにして外部からのインスタンス生成を防止
private SingletonEager() {}
// クラスロード時にインスタンスを生成(staticによりスレッドセーフ)
private static final SingletonEager instance = new SingletonEager();
// インスタンスを返すパブリックメソッド
public static SingletonEager getInstance() {
return instance;
}
}
この方法は、インスタンスが使用されるかどうかに関わらず、クラスロード時に生成されます。使用時に生成したい場合は、後述の遅延初期化方式を検討します。
3.2 遅延初期化(Lazy Initialization)
この方式はスレッドセーフではない(synchronizedで解決可能)ですが、遅延ロードが可能です。ただし、呼び出し効率が低下します。
public class SingletonLazy {
private SingletonLazy() {}
private static SingletonLazy instance;
// synchronizedを使用してスレッドセーフを確保するが、パフォーマンスに影響
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
synchronizedによるロックがパフォーマンスを低下させるため、次に紹介するDCL(Double-Checked Locking)方式が考案されました。
3.3 ダブルチェックロッキング(DCL)
public class SingletonDCL {
private SingletonDCL() {}
private static volatile SingletonDCL instance;
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
DCLはメソッド全体をロックするのではなく、ロック範囲を限定することで効率を向上させます。しかし、Javaメモリモデル(JMM)の影響で、インスタンス生成がアトミック操作ではないため、問題が発生する可能性があります。インスタンス生成は通常以下の3ステップで行われます:
- メモリ割り当て
- コンストラクタ実行
- 参照の割り当て
コンパイラやプロセッサの命令並べ替え(リオーダリング)により、上記の順序が変わることがあります。volatileキーワードを使用することで、このリオーダリング問題を大幅に軽減できます。
3.4 静的内部クラス(Static Inner Class)
public class SingletonInner {
private SingletonInner() {}
private static class Holder {
private static final SingletonInner instance = new SingletonInner();
}
public static SingletonInner getInstance() {
return Holder.instance;
}
}
この方式は、外部クラスにstaticプロパティがないため、クラスロード時に即座に初期化されません。getInstance()メソッドが呼ばれたときに初めて内部クラスがロードされ、その際にインスタンスが生成されます。クラスローダーによるロードはスレッドセーフであるため、同期の問題がなく、遅延ロードも実現できます。
しかし、Javaのリフレクション機構を使用すると、privateフィールドやコンストラクタにアクセスでき、シングルトンを破壊できます。
// リフレクションによる破壊例
Constructor<SingletonInner> constructor = SingletonInner.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonInner instance2 = constructor.newInstance();
// instance2はシングルトンと異なるインスタンスになる
3.5 列挙型(Enum)
列挙型はJDK 1.5から導入され、スレッドセーフで呼び出し効率が高いですが、遅延ロードはできません。リフレクションでも破壊できないため、最も堅牢なシングルトン実装とされています。
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
// 使用例
SingletonEnum instance1 = SingletonEnum.INSTANCE;
SingletonEnum instance2 = SingletonEnum.INSTANCE;
System.out.println(instance1 == instance2); // true
リフレクションでインスタンスを生成しようとすると、newInstance()メソッドの内部で例外がスローされるよう設計されています。