シングルトンのスレッドセーフなレイジー初期化について話している場合は、同期コードなしで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()
ます。 loadは内部クラスをロードします。
余談ですが、@Singleton
そのような問題を処理するアノテーションが存在する日を楽しみにしています。
編集:
特定の不信者は、ラッパークラスは「何もしない」と主張しています。これは、特別な状況下ではありますが、それが重要であるという証拠です。
基本的な違いは、ラッパークラスバージョンでは、ラッパークラスがロードされたときにシングルトンインスタンスが作成されます。これは、最初の呼び出しgetInstance()
が行われたときに作成されますが、ラップされていないバージョン(つまり、単純な静的初期化)では、インスタンスが作成されます。メインクラスがロードされたとき。
getInstance()
メソッドの呼び出しが単純な場合、ほとんど違いはありません。違いは、ラップされたバージョンを使用する場合、インスタンスが作成される前に他のすべてのsttic初期化が完了していることですが、これは、ソースの最後にリストされている静的インスタンス変数。
ただし、クラスを名前でロードしている場合、ストーリーはまったく異なります。Class.forName(className)
静的初期化が発生するように指示するクラスを呼び出すため、使用するシングルトンクラスがサーバーのプロパティである場合、単純なバージョンでは、が呼び出されたときではなく、がClass.forName()
呼び出されたときに静的インスタンスが作成されます。インスタンスを取得するためにリフレクションを使用する必要があるため、これは少し不自然なことですが、それでも、私の競合を示す完全に機能するコードがいくつかあります(次の各クラスはトップレベルのクラスです)。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()
が呼び出されると、ラップされていない単純な実装が作成されます。これは、静的初期化を実行する準備が整う前の場合があります。