シングルトンパターンの基本要件
シングルトンパターンは、特定のクラスに対してインスタンスが1つだけ生成されることを保証する creational パターンです。主な特徴は以下の通りです:
- クラス自身が唯一のインスタンスを管理する。
- 外部からのインスタンス生成を禁止し、グローバルに共通のインスタンスを提供する。
- 遅延初期化(lazy)または即時初期化(eager)で実装可能。
即時初期化:Eager Initialization
クラスがロードされた時点でインスタンスを生成する方法です。スレッドセーフであり、シンプルですが、使用しない場合でもメモリを消費します。
public class EagerInitSingleton {
private static final EagerInitSingleton INSTANCE = new EagerInitSingleton();
private EagerInitSingleton() { }
public static EagerInitSingleton getInstance() {
return INSTANCE;
}
}
この方式は「空間を犠牲にして時間を得る」アプローチと言えます。起動時に初期化されるため、初回アクセス時のコストはゼロですが、無駄なリソース消費の可能性があります。
遅延初期化:Synchronized Method
初めてgetInstance()が呼び出されるまでインスタンスを作成しません。ただし、すべての呼び出しで同期処理を行うため、パフォーマンスに影響が出ます。
public class SyncLazySingleton {
private static SyncLazySingleton instance;
private SyncLazySingleton() { }
public static synchronized SyncLazySingleton getInstance() {
if (instance == null) {
instance = new SyncLazySingleton();
}
return instance;
}
}
この実装は「時間よりも空間を優先」しています。未使用時はインスタンスが作られないためメモリ効率は良いですが、毎回のメソッド呼び出しにロックが発生します。
ダブルチェックロック(Double-Checked Locking)
初回のみ同期を行い、その後はロックなしで高速にアクセスできるよう設計されています。ただし、volatileキーワードの使用が必須です。
public class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() { }
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
volatileにより、インスタンスの初期化順序が正しく保たれ、複数スレッド間での可視性が保証されます。しかし、JVMの最適化抑制の副作用があるため、必要最小限での使用が推奨されます。
静的内部クラスによる遅延読み込み(Initialization-on-demand Holder)
JVMのクラス初期化機構を利用した、非常に効率的な実装です。内部クラスが初めて参照されるときにのみインスタンスが生成され、スレッドセーフかつ同期不要です。
public class HolderSingleton {
private HolderSingleton() { }
private static class InstanceHolder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
}
この方法では、getInstance()呼び出しによってInstanceHolderが初めて参照され、その際にJVMが自動的にスレッドセーフな初期化を行います。コードも簡潔で、パフォーマンス上のオーバーヘッドがありません。
列挙型(enum)による実装
『Effective Java』で推奨されている最も安全な実装方法です。シリアライズやリフレクション攻撃に対しても頑健です。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 処理内容
}
}
利用は非常に簡単で、EnumSingleton.INSTANCE.doSomething()のように呼び出せます。JVMレベルでインスタンスの重複を防ぎ、シリアライズ時にも自動的に正しいインスタンスを維持します。