マルチスレッド環境でシングルトンクラスを使用するための好ましい方法は何ですか?
私が3つのスレッドを持っていて、それらすべてが同時にシングルトンクラスのgetInstance()
メソッドにアクセスしようとしていると仮定します-
synchronized
getInstance()
メソッドを使用するか、getInstance()
内でsynchronized
ブロックを使用することをお勧めしますか?.他に方法がある場合はお知らせください。
本当にスレッドセーフにしたい場合、タスクは理論的には簡単ではありません。
問題に関する非常に素晴らしい論文が見つかりました @ IBM
シングルトンを取得するだけでは、読み取りだけなので、同期は必要ありません。したがって、同期の設定を同期するだけで十分です。 2つのトレッドが起動時に同時にシングルトンを作成しようとしない限り、最悪の場合のシナリオでインスタンスがリセットされないように、インスタンスが2回設定されているかどうかを確認する必要があります。
次に、JITコンパイラが順不同の書き込みを処理する方法を考慮する必要がある場合があります。とにかく100%スレッドセーフではありませんが、このコードはある程度ソリューションに近いものになります。
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
Singleton inst = instance;
if (inst == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
}
}
return instance;
}
だから、あなたはおそらく怠惰でないものに頼るべきです:
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
または、少し肥大化しますが、より柔軟な方法は、静的シングルトンの使用を回避し、 Spring などのインジェクションフレームワークを使用して「シングルトン風」オブジェクトのインスタンス化を管理します(遅延初期化を構成できます) )。
スレッドセーフ 、 遅延初期化シングルトン について話している場合、これは同期コードなしの100%スレッドセーフの遅延初期化:
_public class MySingleton {
private static class MyWrapper {
static MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return MyWrapper.INSTANCE;
}
}
_
これは、getInstance()
が呼び出されたときにのみシングルトンをインスタンス化し、100%スレッドセーフです!それは古典的です。
これは、クラスローダーがクラスの静的初期化を処理するための独自の同期を持っているために機能します。クラスが使用される前にすべての静的初期化が完了していることが保証されます。このコードでは、クラスはgetInstance()
内でのみ使用されますメソッドなので、ロードされたクラスが内部クラスをロードすると、that'sとなります。
余談ですが、このような問題を処理する_@Singleton
_アノテーションが存在する日を楽しみにしています。
特定の不信者は、ラッパークラスが「何もしない」と主張しました。これは、特別な状況ではありますが、does事項であることの証明です。
基本的な違いは、ラッパークラスのバージョンでは、ラッパークラスが読み込まれるときにシングルトンインスタンスが作成され、最初の呼び出しでgetInstance()
が作成されますが、ラップされていないバージョンでは静的初期化-インスタンスは、メインクラスが読み込まれたときに作成されます。
getInstance()
メソッドを単純に呼び出すだけの場合、almost違いはありません-違いはすべてのothersttic初期化が完了したはずですbeforeラップされたバージョンを使用するとインスタンスが作成されますが、これは単に静的ソースにリストされたインスタンス変数last.
ただし、クラスをnameでロードしている場合、ストーリーはかなり異なります。静的な初期化が発生するのを待つクラスでClass.forName(className)
を呼び出すため、使用するシングルトンクラスがサーバーのプロパティである場合、単純なバージョンでは、Class.forName()
のときに静的インスタンスが作成されますnotが呼び出されたとき、getInstance()
が呼び出されたとき。インスタンスを取得するためにリフレクションを使用する必要があるため、これは少し工夫されていることを認めますが、それでも、私の競合を示すいくつかの完全な作業コードがあります(以下のクラスはそれぞれトップレベルのクラスです)。
_public abstract class BaseSingleton {
private long createdAt = System.currentTimeMillis();
public String toString() {
return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
}
}
public class EagerSingleton extends BaseSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
public class LazySingleton extends BaseSingleton {
private static class Loader {
static final LazySingleton INSTANCE = new LazySingleton();
}
public static LazySingleton getInstance() {
return Loader.INSTANCE;
}
}
_
そしてメイン:
_public static void main(String[] args) throws Exception {
// Load the class - assume the name comes from a system property etc
Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");
Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()
// Invoke the getInstace method on the class
BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);
System.out.println(lazySingleton);
System.out.println(eagerSingleton);
}
_
出力:
_LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago
_
ご覧のとおり、Class.forName()
が呼び出されると、ラップされていない単純な実装が作成されます。これは、beforeになる可能性があります実行されました。
シングルトンを遅延初期化する場合のみ、getInstance
内で同期が必要です。スレッドが開始される前にインスタンスを作成できた場合は、参照が不変になるため、ゲッターで同期を削除できます。もちろん、シングルトンオブジェクト自体が変更可能な場合は、同時に変更できる情報にアクセスするメソッドを同期する必要があります。
効果的なJava=で説明されている最良の方法は、次のとおりです。
public class Singelton {
private static final Singelton singleObject = new Singelton();
public Singelton getInstance(){
return singleObject;
}
}
同期の必要はありません。
この質問は、インスタンスが作成される方法とタイミングによって異なります。 getInstance
メソッドが遅延初期化する場合:
_if(instance == null){
instance = new Instance();
}
return instance
_
次に、同期する必要があります。そうしないと、複数のインスタンスが発生する可能性があります。通常、この問題は Double Checked Locking に関する話で扱われます。
それ以外の場合は、静的インスタンスを事前に作成すると
_private static Instance INSTANCE = new Instance();
_
その場合、getInstance()
メソッドの同期は必要ありません。
Effective Javaで提案されているようにEnumを使用する人はいません。
あなたのJavaランタイムが新しいJMM(Javaメモリモデル、おそらく5.0より新しい)を使用しているであると確信している場合、ダブルチェックロックは問題ありませんが、それ以外の場合は、ボヘミアンが言ったように静的内部クラスを使用するか、フロリアンサリホビッチが言ったように「効果的なJava」の列挙型を使用する方がよいでしょう。
簡単にするために、私はenumクラスを使用する方が良い方法だと思います。同期を行う必要はありません。 Java構成によって、定数にアクセスしようとしているスレッドの数に関係なく、常に1つの定数のみが作成されるようにします。
参考までに、シングルトンを他の実装と交換する必要がある場合があります。次に、クラスを変更する必要があります。これは、Open Closeプリンシパルの違反です。シングルトンの問題は、プライベートコンストラクターがあるため、クラスを拡張できないことです。したがって、クライアントがインターフェースを介して話していることは、より良い習慣です。
列挙型クラスとインターフェイスを使用したシングルトンの実装:
Client.Java
public class Client{
public static void main(String args[]){
SingletonIface instance = EnumSingleton.INSTANCE;
instance.operationOnInstance("1");
}
}
SingletonIface.Java
public interface SingletonIface {
public void operationOnInstance(String newState);
}
EnumSingleton.Java
public enum EnumSingleton implements SingletonIface{
INSTANCE;
@Override
public void operationOnInstance(String newState) {
System.out.println("I am Enum based Singleton");
}
}