😂

デザインパターン入門Singletonパターンについて

2023/04/30に公開

1. Singletonパターンの概要

Singletonパターンとは何か

Singletonパターンは、インスタンスを1つだけ生成するデザインパターンのことです。インスタンスが必要になる度に新しく生成するのではなく、1つのインスタンスを共有することで、メモリ使用量を節約したり、グローバルな状態を管理したりすることができます。

Singletonパターンの特徴

Singletonパターンの特徴は以下の通りです。

  • インスタンスを1つだけ生成する。
  • 生成されたインスタンスをグローバルにアクセス可能にする。
  • インスタンス生成時に行う処理を共通化する。

Singletonパターンの利用例

Singletonパターンは、データベースのコネクションプールや、ログ出力処理、システム設定の管理など、インスタンスを1つだけ生成する必要がある場面で利用されます。また、スレッドセーフなSingletonパターンを実装することで、マルチスレッド環境での安全性を確保することもできます。

2. Singletonパターンの実装方法

遅延初期化による実装

遅延初期化とは、必要になるまでオブジェクトを生成しない方法です。Singletonパターンの実装では、getInstance()メソッドが呼ばれた時に初めてインスタンスを生成する遅延初期化の実装がよく使われます。

public class Singleton {
    private static Singleton instance = null;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

この実装では、instanceフィールドがnullの場合のみSingletonのインスタンスを生成するようになっています。そして、instanceフィールドがnullであるかどうかを判定するためにsynchronizedキーワードを付けたメソッドとしてgetInstance()メソッドを定義しています。synchronizedキーワードを付けることで、複数のスレッドから同時にgetInstance()メソッドが呼ばれた場合に、インスタンスの生成が複数回行われないようにすることができます。

初期化済みインスタンスを静的フィールドに持つ実装

この実装方法では、Singletonクラスのインスタンスをstaticフィールドに保持することで、Singletonクラスが初めてロードされた時にインスタンスを生成する方法を採用しています。静的フィールドであるため、getInstance()メソッドから直接インスタンスにアクセスすることができます。

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

この実装では、getInstance()メソッドの中でinstanceフィールドを返すだけであり、インスタンスを生成する必要がありません。そのため、getInstance()メソッドはsynchronizedキーワードを使う必要がないため、パフォーマンスの観点からも有効な方法です。

3. Singletonパターンの実装上の注意点

マルチスレッド環境での実装

Singletonパターンは、システム全体で唯一のインスタンスを保持するため、マルチスレッド環境下では同期化に注意が必要です。

1. 遅延初期化の場合

以下のようにsynchronizedを用いた同期化を行うことで、マルチスレッド環境下でも適切に動作するSingletonクラスを実装することができます。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

ただし、synchronizedはメソッド全体を同期化するため、getInstance()メソッドを呼び出すたびにロックがかかり、パフォーマンスに影響が出る可能性があります。

2. 静的フィールドによる初期化の場合

以下のように、クラスロード時にインスタンスを生成することで、スレッドセーフなSingletonクラスを実装することができます。

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

クラスロード時にインスタンスを生成するため、getInstance()メソッドが呼ばれる前にインスタンスが生成されているため、スレッドセーフな実装となっています。

リフレクションによる攻撃への対策

リフレクションによる攻撃とは、クラス内部のフィールドやメソッドにアクセスするためにJavaのリフレクション機能を用いる攻撃手法です。Singletonパターンでは、getInstanceメソッドから常に同じインスタンスを返すように実装されていますが、リフレクション機能を使用することで、privateなコンストラクタを呼び出して新しいインスタンスを生成することができます。

この攻撃に対処するために、コンストラクタ内部でインスタンスが既に生成されている場合には、新しいインスタンスを生成しないようにすることが一般的です。以下は、初期化済みインスタンスを静的フィールドに持つ実装に対して、リフレクションによる攻撃に対処する方法の例です。

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static Singleton getInstance() {
        return instance;
    }
}

コンストラクタ内部でinstanceがnullであることをチェックし、既にインスタンスが生成されている場合にはIllegalStateExceptionをスローしています。このようにすることで、リフレクションによる攻撃を防止することができます。ただし、この方法では例外処理が必要なため、パフォーマンスの影響が出る可能性があります。

4. まとめ

Singletonパターンのメリット

Singletonパターンの最大のメリットは、インスタンスが常に1つしか存在しないことを保証できることです。そのため、インスタンス数を制御する必要がある場合や、リソースを共有する必要がある場合に有効なデザインパターンとなります。また、クラス内部でインスタンス化されるため、システム内でどこからでも簡単にアクセスできるという利点があります。

Singletonパターンのデメリット

Singletonパターンのデメリットは、テストが困難になる可能性があることです。また、複数のクラスで参照される場合、依存関係が生まれるため、システムの変更に弱くなる可能性があります。さらに、シングルトンクラスに大量の処理を追加すると、クラスの責務が複雑化し、保守性が低下する可能性があります。

Discussion