java-recipes

ホーム デザインパターン › DP-05

DP-05: Singleton パターン

アプリケーション全体でインスタンスを1つに制限するパターンです。java.lang.Runtime など 標準ライブラリでも使われています。実装方法は複数あり、それぞれの特徴を理解して使い分けることが大切です。

Singleton パターンとは

設定情報・ログ出力・データベース接続プールなど、アプリケーション全体で1つだけ存在すべきオブジェクトに使います。 コンストラクタを private にして外部からの生成を禁止し、getInstance() メソッドで唯一のインスタンスを返します。

パターン遅延初期化スレッドセーフシンプルさ
Eager Initialization
Lazy(スレッドアンセーフ)△(使用非推奨)
Double-Checked Locking✅(volatile 必須)
Holder(推奨)
Enum Singleton◎(最もシンプル)

通常は Holder パターンEnum Singleton を使いましょう。 Holder パターンは遅延初期化(初めて使われたときに生成)とスレッドセーフを同時に達成できます。 Enum Singleton はさらにシンプルで、直列化(Serializable)の問題も自動的に解決されます。

サンプルコード

SingletonPatternSample.java
public class SingletonPatternSample {

    // パターン1: Eager Initialization(クラスロード時に生成)
    static class EagerSingleton {
        private static final EagerSingleton INSTANCE = new EagerSingleton();
        private int count = 0;

        private EagerSingleton() {} // コンストラクタを private

        public static EagerSingleton getInstance() {
            return INSTANCE;
        }

        public void increment() { count++; }
        public int getCount() { return count; }
    }

    // パターン2: Lazy Initialization(スレッドアンセーフ - ❌)
    static class LazySingletonUnsafe {
        private static LazySingletonUnsafe instance = null;

        private LazySingletonUnsafe() {}

        public static LazySingletonUnsafe getInstance() {
            if (instance == null) { // ❌ 複数スレッドから同時に呼ばれると2つ生成される
                instance = new LazySingletonUnsafe();
            }
            return instance;
        }
    }

    // パターン3: Double-Checked Locking(スレッドセーフ)
    static class ThreadSafeSingleton {
        private static volatile ThreadSafeSingleton instance = null; // volatile 必須

        private ThreadSafeSingleton() {}

        public static ThreadSafeSingleton getInstance() {
            if (instance == null) {
                synchronized (ThreadSafeSingleton.class) {
                    if (instance == null) { // ダブルチェック
                        instance = new ThreadSafeSingleton();
                    }
                }
            }
            return instance;
        }
    }

    // パターン4: Initialization-on-demand holder(推奨 - シンプルでスレッドセーフ)
    static class HolderSingleton {
        private int value = 42;

        private HolderSingleton() {}

        private static class Holder {
            // クラスロード時に JVM が安全に1回だけ初期化
            static final HolderSingleton INSTANCE = new HolderSingleton();
        }

        public static HolderSingleton getInstance() {
            return Holder.INSTANCE;
        }

        public int getValue() { return value; }
    }

    // パターン5: Enum Singleton(最もシンプルで安全)
    enum EnumSingleton {
        INSTANCE;

        private int count = 0;

        public void increment() { count++; }
        public int getCount() { return count; }
    }

    public static void main(String[] args) {
        System.out.println("=== Eager Singleton ===");
        EagerSingleton s1 = EagerSingleton.getInstance();
        EagerSingleton s2 = EagerSingleton.getInstance();
        s1.increment();
        s1.increment();
        System.out.println("同一インスタンス: " + (s1 == s2));
        System.out.println("s2.getCount(): " + s2.getCount()); // 2(同じインスタンス)

        System.out.println("\n=== Holder Singleton(推奨)===");
        System.out.println(HolderSingleton.getInstance().getValue());

        System.out.println("\n=== Enum Singleton ===");
        EnumSingleton.INSTANCE.increment();
        EnumSingleton.INSTANCE.increment();
        System.out.println("count: " + EnumSingleton.INSTANCE.getCount()); // 2

        System.out.println("\n=== java.lang.Runtime の例 ===");
        Runtime rt = Runtime.getRuntime(); // Singleton の実例
        System.out.println("最大メモリ: " + rt.maxMemory() / 1024 / 1024 + " MB");
    }
}

Java 8 では Holder パターン(Initialization-on-demand holder)が推奨です。volatile や synchronized を使わずに遅延初期化とスレッドセーフを両立できます。

よくあるミス・注意点

⚠️ Lazy + スレッドアンセーフな実装(if == null チェックのみ)

if (instance == null) { instance = new ... } だけでは、 複数のスレッドが同時に null チェックを通過してしまい、 2つ以上のインスタンスが生成される可能性があります。 スレッドセーフが必要な場合は Holder パターンか Enum Singleton を使いましょう。

⚠️ Double-Checked Locking で volatile を忘れる

volatile を付けないと、 CPU の命令並べ替え最適化(リオーダリング)によって初期化が完了していないインスタンスが 別スレッドから参照されることがあります。 Double-Checked Locking を使う場合は private static volatile が必須です。

⚠️ Singleton にグローバルな可変状態を持たせすぎる

Singleton は便利な反面、テスト時にモック(偽物のオブジェクト)への差し替えが難しくなります。 また、スレッドセーフを保つために同期化のコストも発生します。 本当に1つである必要があるかどうかを設計時に確認しましょう。

テストする観点

  • getInstance() を複数回呼んでも同一インスタンス(== が true)が返ること
  • 複数スレッドから同時に getInstance() を呼んでも同一インスタンスが返ること(スレッドセーフ)
  • 1つのスレッドでインクリメントした値を別のスレッドから取得すると同じ値になること(状態の共有)
  • Enum Singleton はシリアライズ・デシリアライズ後も同一インスタンスであること(境界値)
  • コンストラクタが private であるため、リフレクションを除いて外部から直接生成できないこと

GitHub でソースコードを見る →