この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:7
ページ更新者:atom
更新日時:2026-06-11 07:12:00

タイトル: シングルトン
SEOタイトル: Singleton パターン完全ガイド (Java / C# / Python / PHP の実装と落とし穴)

この記事の要点
  • Singleton はクラスのインスタンスが プロセス内で 1 つだけであることを保証するデザインパターン
  • 基本実装: private コンストラクタ + static フィールド + static getInstance()
  • スレッドセーフ実装: Double-Checked Locking / 静的初期化 (Java) / Lazy (C#) / Enum Singleton
  • Python は module-level インスタンスがシングルトンの代替として広く使われる
  • 現代では DI コンテナ (Spring / Laravel / NestJS) によるライフサイクル管理の方が一般的

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 = &quot;postgres://...&quot;
api_key = &quot;secret&quot;

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 = &quot;postgres://...&quot;

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 &quot;...&quot;; }
}

@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 にして毎回リセット。