Javaのシングルトン
1. 前書き
この簡単な記事では、プレーンJavaでシングルトンを実装する最も一般的な2つの方法について説明します。
2. クラスベースのシングルトン
最も一般的なアプローチは、通常のクラスを作成し、次のことを確認してシングルトンを実装することです。
-
プライベートコンストラクター
-
唯一のインスタンスを含む静的フィールド
-
インスタンスを取得するための静的ファクトリーメソッド
後で使用するために、infoプロパティも追加します。 したがって、実装は次のようになります。
public final class ClassSingleton {
private static ClassSingleton INSTANCE;
private String info = "Initial info class";
private ClassSingleton() {
}
public static ClassSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
// getters and setters
}
これは一般的なアプローチですが、can be problematic in multithreading scenariosであることに注意することが重要です。これが、シングルトンを使用する主な理由です。
簡単に言えば、複数のインスタンスが発生し、パターンのコア原則が破られる可能性があります。 この問題にはロックソリューションがありますが、次のアプローチではこれらの問題をルートレベルで解決します。
3. 列挙型シングルトン
今後は、列挙を使用するという別の興味深いアプローチについては説明しません。
public enum EnumSingleton {
INSTANCE("Initial class info");
private String info;
private EnumSingleton(String info) {
this.info = info;
}
public EnumSingleton getInstance() {
return INSTANCE;
}
// getters and setters
}
このアプローチには、enum実装自体によって保証されたシリアル化とスレッドセーフがあります。これにより、クラスベースの実装で指摘された問題が修正され、単一のインスタンスのみが利用できるようになります。
4. 使用法
ClassSingletonを使用するには、インスタンスを静的に取得する必要があります。
ClassSingleton classSingleton1 = ClassSingleton.getInstance();
System.out.println(classSingleton1.getInfo()); //Initial class info
ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");
System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info
EnumSingletonについては、他のJava列挙型と同じように使用できます。
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();
System.out.println(enumSingleton1.getInfo()); //Initial enum info
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");
System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info
5. よくある落とし穴
シングルトンは一見シンプルなデザインパターンであり、シングルトンを作成するときにプログラマが犯す可能性のある一般的な間違いはほとんどありません。
シングルトンでは2種類の問題を区別します。
-
実存的(シングルトンが必要ですか?)
-
実装(適切に実装していますか?)
5.1. 存在する問題
概念的には、シングルトンは一種のグローバル変数です。 一般に、グローバル変数は回避する必要があることがわかっています。特に状態が可変の場合はそうです。
シングルトンを使用してはいけないと言っているのではありません。 ただし、コードを整理するためのより効率的な方法があるかもしれないと言っています。
メソッドの実装がシングルトンオブジェクトに依存している場合は、それをパラメーターとして渡してみませんか? この場合、メソッドが依存するものを明示的に示します。 結果として、テストを実行する際に(必要に応じて)これらの依存関係を簡単にモックすることができます。
たとえば、シングルトンは、アプリケーションの構成データ(つまり、リポジトリへの接続)を包含するためによく使用されます。 グローバルオブジェクトとして使用される場合、テスト環境の構成を選択することが困難になります。
したがって、テストを実行すると、実稼働データベースはテストデータで損なわれますが、これはほとんど受け入れられません。
シングルトンが必要な場合、そのインスタンス化を別のクラスに委任する可能性を検討するかもしれません-一種のファクトリー-シングルトンのインスタンスが1つだけ存在することを保証する必要があります。
5.2. 実装上の問題
シングルトンは非常に単純に見えますが、その実装にはさまざまな問題があります。 すべての結果として、クラスのインスタンスが複数ある場合があります。
Synchronization上記で示したプライベートコンストラクターを使用した実装はスレッドセーフではありません。シングルスレッド環境ではうまく機能しますが、マルチスレッド環境では、同期技術を使用してのアトミック性を保証する必要があります。操作:
public synchronized static ClassSingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
メソッド宣言のキーワードsynchronizedに注意してください。 メソッドの本体には、いくつかの操作(比較、インスタンス化、および戻り)があります。
同期がない場合、2つのスレッドが実行をインターリーブして、式INSTANCE == nullが両方のスレッドでtrue と評価され、その結果、ClassSingletonの2つのインスタンスが評価される可能性があります。作成されます。
Synchronizationは、パフォーマンスに大きな影響を与える可能性があります。 このコードが頻繁に呼び出される場合は、lazy initializationやdouble-checked lockingなどのさまざまな手法を使用して高速化する必要があります(コンパイラーの最適化により、これが期待どおりに機能しない場合があることに注意してください)。 詳細については、チュートリアル「https://www.example.com/java-singleton-double-checked-locking [Double-Checked LockingwithSingleton]」をご覧ください。
Multiple Instances JVM自体に関連するシングルトンには、他にもいくつかの問題があり、シングルトンの複数のインスタンスが発生する可能性があります。 これらの問題は非常に微妙であり、それぞれについて簡単に説明します。
-
シングルトンは、JVMごとに一意であると想定されています。 これは、分散システムまたは内部が分散テクノロジーに基づいているシステムの場合に問題になる可能性があります。
-
すべてのクラスローダーは、シングルトンのバージョンをロードする場合があります。
-
シングルトンは、誰もそれへの参照を保持しないと、ガベージコレクションされる場合があります。 この問題により、一度に複数のシングルトンインスタンスが存在することはありませんが、再作成すると、インスタンスが以前のバージョンと異なる場合があります。
6. 結論
このクイックチュートリアルでは、コアJavaのみを使用してシングルトンパターンを実装する方法、一貫性を確保する方法、およびこれらの実装を利用する方法に焦点を当てました。
これらの例の完全な実装は、over on GitHubにあります。