はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、生成に関するパターンの中でも最もシンプルで馴染み深いSingleton(シングルトン)パターンを、Javaのサンプルコードとともに解説します。
解決したい課題
アプリケーション全体で、あるクラスのインスタンスがただ1つだけ存在してほしい、という場面があります。
- アプリケーション全体の設定情報を保持するクラス
- ログ出力を一元管理するクラス
- DBへのコネクションプールを管理するクラス
こうしたクラスを普通にnewで複数回インスタンス化できる状態にしておくと、「設定値の食い違い」「リソースの二重確保」といった問題が起こりえます。
Singletonパターンは、インスタンスの生成をクラス自身が管理し、常に同じインスタンスを返すことでこの問題を解決します。
クラス構成
Singletonパターンの実装は非常にシンプルです。
- コンストラクタを
privateにして、外部からnewできないようにする - クラス自身が、唯一のインスタンスを保持する静的フィールドを持つ
- そのインスタンスを取得するための、静的メソッド(
getInstance()等)を用意する
Javaでの実装例
基本形(遅延初期化・スレッドセーフではない版)
public class Singleton {
// このクラス唯一のインスタンスを保持する静的フィールド
private static Singleton instance;
// コンストラクタをprivateにし、外部からのnewを禁止する
private Singleton() {
System.out.println("Singletonインスタンスを生成しました");
}
// インスタンスを取得するための唯一の入り口
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void doSomething() {
System.out.println("何らかの処理を実行");
}
}
public class Main {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
s1.doSomething();
// s1とs2は同じインスタンスであることを確認
System.out.println(s1 == s2); // true
}
}
実行結果:
Singletonインスタンスを生成しました
何らかの処理を実行
true
getInstance()を2回呼んでいますが、「Singletonインスタンスを生成しました」というメッセージは1回しか表示されません。
2回目の呼び出しでは、すでに生成済みのインスタンスがそのまま返されているためです。
マルチスレッド環境での注意点
上記の基本形には、複数のスレッドが同時にgetInstance()を呼んだ場合、instance == nullの判定が同時に成立してしまい、インスタンスが2つ作られてしまうという不具合の可能性があります。
この問題への代表的な対処法が以下の2つです。
対処法1: synchronizedで排他制御する
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
確実ですが、getInstance()が呼ばれるたびに毎回ロックの取得・解放が発生するため、呼び出し頻度が高い場合はわずかにパフォーマンスへの影響があります。
対処法2: 初期化時にインスタンスを生成しておく(推奨されることが多い)
public class EagerSingleton {
// クラスロード時に、あらかじめインスタンスを生成しておく
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
クラスがロードされるタイミングでJVMが1度だけインスタンス化を保証してくれるため、synchronizedを使わずにスレッドセーフになります。
インスタンスの生成コストが小さい場合は、この方式がシンプルでよく使われます。
対処法3: enumを使う(最もシンプルで安全とされる方式)
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("何らかの処理を実行");
}
}
EnumSingleton.INSTANCE.doSomething();
Javaのenumは、言語仕様上インスタンス化が1回だけであることが保証されており、リフレクションやシリアライズ経由での複数生成にも強い、堅牢なSingleton実装として紹介されることがよくあります。
使用場面
- アプリケーション全体の設定(コンフィグ)を保持するクラス
- ログ出力機能を提供するクラス
- DB接続プールやスレッドプールなど、コストの高いリソースを管理するクラス
- キャッシュを一元管理するクラス
注意点・デメリット
Singletonパターンは手軽に使える一方、批判されることも多いパターンです。
- グローバルな状態を持ち込んでしまう: どこからでもアクセスできる状態は、コードの見通しを悪くし、テストしづらくする原因になりやすい
- 単体テストが難しくなる: Singletonに依存したコードは、テスト用の差し替え(モック化)がしにくい
- 隠れた依存関係を作りやすい: コンストラクタ引数として渡されないため、そのクラスが何に依存しているかがコードから読み取りにくくなる
そのため近年は、Singletonパターンをクラス自身に実装するのではなく、DI(依存性注入)コンテナ(SpringのBeanスコープsingleton等)にインスタンスのライフサイクル管理を任せる設計が好まれる傾向にあります。
考え方(「インスタンスを1つに保つ」)は同じでも、実現方法として直接この記事のようなコードを書く機会は、フレームワークを使う現場では少なくなってきています。
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | あるクラスのインスタンスを、アプリケーション全体でただ1つに保ちたい |
| 実現方法 | コンストラクタをprivateにし、静的メソッド経由でのみインスタンスを取得できるようにする |
| 注意点 | マルチスレッド環境での安全性、グローバル状態によるテストのしにくさ |
| 現代的な代替 | DIコンテナのシングルトンスコープに任せる設計が好まれる傾向にある |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


