| この記事の要点 |
|
Singleton パターンとは
Singleton は GoF (Gang of Four) のデザインパターンの 1 つで、あるクラスのインスタンスがプロセス内で常に 1 つだけであることを保証するパターンです。設定オブジェクト、ロガー、DB コネクションプール、キャッシュなど「全体で 1 つあれば十分」な対象に使われます。
Singleton の本質は次の 2 点:
- インスタンスが 1 つしか作られないことを保証
- そのインスタンスへのグローバルアクセス手段を提供
基本的な実装 (Java)
public class AppConfig {
// 1. 唯一のインスタンスを保持する static フィールド
private static AppConfig instance;
// 2. 外部から new させないため private コンストラクタ
private AppConfig() {
// 設定ファイル読み込み等
}
// 3. 取得用 static メソッド
public static AppConfig getInstance() {
if (instance == null) {
instance = new AppConfig();
}
return instance;
}
public String getDbUrl() { return "jdbc:..."; }
}
// 使用
AppConfig cfg = AppConfig.getInstance();
String url = cfg.getDbUrl();
これは シングルスレッド前提の基本形。マルチスレッドでは 2 つのスレッドが同時に if (instance == null) を通過し、インスタンスが 2 つできるバグがあります。
スレッドセーフな実装 (Java)
方式 1: synchronized メソッド (シンプルだが遅い)
public static synchronized AppConfig getInstance() {
if (instance == null) {
instance = new AppConfig();
}
return instance;
}
方式 2: Double-Checked Locking
public class AppConfig {
private static volatile AppConfig instance; // ★ volatile 必須
private AppConfig() {}
public static AppConfig getInstance() {
if (instance == null) { // 1 回目チェック (ロック無し)
synchronized (AppConfig.class) {
if (instance == null) { // 2 回目チェック (ロック内)
instance = new AppConfig();
}
}
}
return instance;
}
}
方式 3: 静的初期化 (eager)
クラスロード時にインスタンス生成。Java のクラスローダがスレッドセーフを保証:
public class AppConfig {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() {}
public static AppConfig getInstance() {
return INSTANCE;
}
}
方式 4: Initialization-on-demand Holder (Lazy + スレッドセーフ)
public class AppConfig {
private AppConfig() {}
private static class Holder {
static final AppConfig INSTANCE = new AppConfig();
}
public static AppConfig getInstance() {
return Holder.INSTANCE;
}
}
方式 5: Enum Singleton (推奨)
Joshua Bloch "Effective Java" で推奨されている方法。リフレクション・シリアライズ攻撃にも耐性:
public enum AppConfig {
INSTANCE;
private final String dbUrl = "jdbc:...";
public String getDbUrl() { return dbUrl; }
}
// 使用
String url = AppConfig.INSTANCE.getDbUrl();
C# の実装
.NET 4+ では Lazy<T> がベストプラクティス:
public sealed class AppConfig
{
private static readonly Lazy<AppConfig> lazy =
new Lazy<AppConfig>(() => new AppConfig());
public static AppConfig Instance => lazy.Value;
private AppConfig() { }
public string DbUrl => "jdbc:...";
}
// 使用
var cfg = AppConfig.Instance;
Python の Singleton (module-level)
Python では モジュール自体がシングルトン。import された結果はキャッシュされ、再利用されます:
# config.py (このモジュール全体がシングルトン)
db_url = "postgres://..."
api_key = "secret"
def get_db_url():
return db_url
# main.py
import config
print(config.get_db_url()) # どこでインポートしても同じインスタンス
クラスとして実装したい場合は __new__ でインスタンス管理:
class AppConfig:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._init()
return cls._instance
def _init(self):
self.db_url = "postgres://..."
a = AppConfig()
b = AppConfig()
print(a is b) # True
# デコレータ版
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Logger:
def log(self, msg): print(msg)
PHP の Singleton
class AppConfig
{
private static ?AppConfig $instance = null;
private string $dbUrl;
private function __construct() {
$this->dbUrl = getenv('DB_URL');
}
// クローン禁止
private function __clone() {}
// unserialize 禁止
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
public static function getInstance(): AppConfig {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getDbUrl(): string {
return $this->dbUrl;
}
}
// 使用
$cfg = AppConfig::getInstance();
echo $cfg->getDbUrl();
Singleton のメリット
- インスタンスが 1 つに限定される → 状態の一貫性
- グローバルアクセスで呼び出しが簡単
- メモリ消費抑制 (大きな初期化コストを 1 回だけ)
- 初期化の遅延 (Lazy) でアプリ起動が高速化
Singleton のデメリット (アンチパターン論)
| 問題 | 説明 |
|---|---|
| グローバル状態 | テストごとに状態が引きずられ テスト困難 |
| Mock しづらい | static 経由のため DI でモック差し替えにくい |
| 密結合 | AppConfig.getInstance() を直接呼ぶコードは AppConfig に強く依存 |
| 並列実行で衝突 | マルチスレッドで競合、ロック設計を間違うとデッドロック |
| サブクラス困難 | インスタンス管理が静的なため、継承で振る舞いを変えにくい |
| 初期化順 | 他の static フィールドと依存関係があると順序問題 |
現代的な代替: DI コンテナ
Spring (Java) / Laravel (PHP) / NestJS (TypeScript) など現代の DI コンテナは、Singleton スコープでインスタンスを管理します。Singleton パターン自体を手書きする機会は激減しました。
// Spring
@Service // デフォルトで Singleton スコープ
public class AppConfig {
public String getDbUrl() { return "..."; }
}
@Component
public class UserService {
private final AppConfig config;
// コンストラクタインジェクション
public UserService(AppConfig config) {
this.config = config;
}
}// Laravel
$this->app->singleton(AppConfig::class, function () {
return new AppConfig();
});
// 使用
$cfg = app(AppConfig::class);
// コントローラでインジェクト
public function index(AppConfig $config) {
return $config->getDbUrl();
}
DI コンテナの利点:
- テスト時に Mock 差し替えが容易
- ライフサイクル管理 (Singleton / Request / Transient) を切り替えやすい
- コードに
getInstance()が散乱しない - 初期化順を自動解決
FAQ
Q: Singleton はアンチパターンと聞いた
A: グローバル状態を作ること自体に問題があるという批判ですが、ロガー・設定・コネクションプール等の用途では今でも妥当。DI コンテナ + Singleton スコープなら問題の多くが解決します。
Q: シングルトンが複数できることはある?
A: ① 複数のクラスローダ (Java)、② リフレクションで private コンストラクタを呼ぶ、③ シリアライズ→デシリアライズ、④ クローン、⑤ マルチプロセス (プロセスごとに別インスタンス)、で破られます。Enum Singleton はリフレクション攻撃に耐性があります。
Q: Singleton と static class はどう違う?
A: 状態を持たない純粋関数なら static、状態・インターフェース実装・継承可能性を持たせるなら Singleton。詳細は別記事「static クラスとシングルトンの違い」へ。
Q: テストでシングルトンの状態が引き継がれて困る
A: テストごとに reset する resetForTest() メソッドを作るか、DI コンテナでスコープを Request にして毎回リセット。
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38