Javaのシングルトン
1前書き
この簡単な記事では、プレーンJavaでシングルトンを実装する2つの最も一般的な方法について説明します。
2クラスベースのシングルトン
最も一般的な方法は、通常のクラスを作成し、それが確実になるようにしてSingletonを実装することです。
-
プライベートコンストラクタ
-
唯一のインスタンスを含む静的フィールド
-
インスタンスを取得するための静的ファクトリメソッド
あとで使用するために、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
}
これは一般的なアプローチですが、マルチスレッドのシナリオでは問題になる可能性があることに注意することが重要です。これが、シングルトンを使用する主な理由です。
簡単に言えば、それは複数のインスタンスをもたらす可能性があり、パターンのコア原則を破ります。この問題には解決策がありますが、次のアプローチでこれらの問題を根本的に解決します。
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 Enumと同じように使用できます。
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. 実存の問題
概念的には、シングルトンは一種のグローバル変数です。一般に、グローバル変数は避けるべきであることを知っています – 特にそれらの状態がミュータブルならば。
-
シングルトンを使用してはいけないと言っているのではありません。しかし、私たちはコードを体系化するためのより効率的な方法があるかもしれないと言っています。
メソッドの実装がシングルトンオブジェクトに依存しているのなら、それをパラメータとして渡してはどうでしょうか。この場合、メソッドが依存しているものを明示的に示します。結果として、私達はテストを実行するときこれらの依存関係を(必要ならば)簡単にモックするかもしれません。
たとえば、シングルトンはアプリケーションの設定データ(つまりリポジトリへの接続)を網羅するためによく使用されます。これらをグローバルオブジェクトとして使用すると、テスト環境の設定を選択するのが難しくなります。
したがって、テストを実行すると、本番データベースにテストデータが保存されなくなりますが、これは許容できません。
シングルトンが必要な場合は、そのインスタンス化を別のクラス(一種のファクトリ)に委譲する可能性を検討することがあります。
5.2. 実施上の問題
シングルトンは非常に単純に見えますが、それらの実装はさまざまな問題を抱えているかもしれません。これらすべての結果として、クラスのインスタンスが2つ以上になる可能性があります。
-
同期** 上記で示したプライベートコンストラクタによる実装はスレッドセーフではありません。シングルスレッド環境ではうまく機能しますが、マルチスレッド環境では操作のアトミック性を保証するために同期技術を使うべきです:
public synchronized static ClassSingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
メソッド宣言のキーワード
synchronized
に注意してください。メソッドの本体にはいくつかの操作(比較、インスタンス化、および戻り)があります。
同期がないと、2つのスレッドが存在する可能性があります。
式
INSTANCEが次のようになるように、それらの実行をインターリーブします。
== null
は両方のスレッドで
__true
__と評価され、その結果、2つのスレッドで評価されます。
ClassSingleton
のインスタンスが作成されます。
同期化はパフォーマンスに大きな影響を与える可能性があります。このコードが頻繁に呼び出される場合は、
lazy initialization
や
double-checked locking
などのさまざまな手法を使用して高速化する必要があります(これはコンパイラの最適化のために期待通りに動作しない可能性があります)。私達は私達の個人指導「https://www.baeldung.com/java-singleton-double-checked-locking[Singletonとの二重チェックされたロック]」のより多くの詳細を見ることができる。
-
複数のインスタンス** JVM自体に関連するシングルトンには、シングルトンの複数のインスタンスが発生する可能性があるその他の問題がいくつかあります。
これらの問題は非常に微妙なので、それぞれについて簡単に説明します。
-
シングルトンはJVMごとに一意であると想定されています. これは問題かもしれません
分散システム、または内部が分散技術に基づいているシステム用。
-
すべてのクラスローダーはそのシングルトンのバージョンをロードするかもしれません.
-
誰も参照を持たないとシングルトンはガベージコレクションされるかもしれません
それに。この問題によって一度に複数のシングルトンインスタンスが存在することはありませんが、再現すると、インスタンスは以前のバージョンと異なる場合があります。
6. 結論
このクイックチュートリアルでは、コアJavaのみを使用してシングルトンパターンを実装する方法、およびそれが一貫していることを確認する方法とこれらの実装を使用する方法に焦点を当てました。
これらの例の完全な実装はhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns[GitHubについて]で見つけることができます。