25

マルチスレッド環境でシングルトンクラスを操作するための好ましい方法は何ですか?

getInstance()私が3つのスレッドを持っていて、それらすべてが同時にシングルトンクラスのメソッドにアクセスしようとしていると仮定します-

  1. 同期が維持されていない場合はどうなりますか?
  2. synchronized getInstance()メソッドを使用するか、synchronized内部でブロックを使用することをお勧めしますかgetInstance()

他に方法があるかどうか教えてください。

4

9 に答える 9

40

シングルトンのスレッドセーフレイジー初期化について話している場合は、同期コードなしで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()が呼び出されると、ラップされていない単純な実装が作成されます。これは、静的初期化を実行する準備が整う前の場合があります。

于 2012-06-17T15:04:49.020 に答える
18

本当にスレッドセーフにしたいのであれば、このタスクは理論的には簡単ではありません。

この問題に関する非常に素晴らしい論文が@IBMにあります

シングルトンを取得するだけでは、読み取りであるため、同期は必要ありません。したがって、同期の設定を同期するだけで十分です。2つのトレッドが起動時に同時にシングルトンを作成しようとしない限り、最悪のシナリオでインスタンスがリセットされないように、インスタンスが2回(同期の外側と内側に1つずつ)設定されているかどうかを確認する必要があります。

次に、JIT(Just-in-time)コンパイラが順不同の書き込みを処理する方法を考慮する必要がある場合があります。このコードは、とにかく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などのインジェクションフレームワークを使用して「シングルトンっぽい」オブジェクトのインスタンス化を管理することです(遅延初期化を構成できます)。

于 2012-06-17T15:17:44.203 に答える
5

getInstanceシングルトンを怠惰に初期化する場合にのみ、内部で同期が必要です。スレッドが開始される前にインスタンスを作成できる場合は、参照が不変になるため、ゲッターで同期をドロップできます。もちろん、シングルトンオブジェクト自体が可変である場合は、同時に変更できる情報にアクセスするメソッドを同期する必要があります。

于 2012-06-17T15:04:37.040 に答える
3

この質問は、インスタンスがいつどのように作成されるかによって異なります。getInstanceメソッドが遅延初期化される場合:

if(instance == null){
  instance = new Instance();
}

return instance

次に、同期する必要があります。そうしないと、複数のインスタンスが発生する可能性があります。この問題は通常、DoubleCheckedLockingに関する講演で扱われます

それ以外の場合は、静的インスタンスを事前に作成します

private static Instance INSTANCE = new Instance();

getInstance()その場合、メソッドの同期は必要ありません。

于 2012-06-17T15:17:45.220 に答える
3

効果的なJavaで説明されている最良の方法は次のとおりです。

public class Singelton {

    private static final Singelton singleObject = new Singelton();

    public Singelton getInstance(){
        return singleObject;
    }
}

同期の必要はありません。

于 2012-11-06T07:41:59.780 に答える
1

効果的なJavaで提案されているように列挙型を使用する人は誰もいませんか?

于 2012-06-17T15:42:25.393 に答える
0

Javaランタイムが新しいJMM(Javaメモリモデル、おそらく5.0より新しい)を使用していることが確実な場合は、ダブルチェックロックで問題ありませんが、インスタンスの前に揮発性ファイルを追加します。それ以外の場合は、Bohemianが言ったように静的内部クラスを使用するか、FlorianSalihovicが言ったように「EffectiveJava」のEnumを使用する方がよいでしょう。

于 2012-06-17T16:29:06.607 に答える
0

簡単にするために、列挙型クラスを使用する方が良い方法だと思います。同期を行う必要はありません。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");
    }
}
于 2017-11-02T21:11:46.507 に答える
0

答えはすでにここで受け入れられていますが、私はあなたの最初の質問に答えるためにテストを共有したいと思います。

同期が維持されていない場合はどうなりますか?

これはSingletonTest、マルチスレッド環境で実行すると完全に災害になるクラスです。

/**
 * @author MILAN
 */
public class SingletonTest
{
    private static final int        PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors();
    private static final Thread[]   THREADS         = new Thread[PROCESSOR_COUNT];
    private static int              instancesCount  = 0;
    private static SingletonTest    instance        = null;

    /**
     * private constructor to prevent Creation of Object from Outside of the
     * This class.
     */
    private SingletonTest()
    {
    }

    /**
     * return the instance only if it does not exist
     */
    public static SingletonTest getInstance()
    {
        if (instance == null)
        {
            instancesCount++;
            instance = new SingletonTest();
        }
        return instance;
    }

    /**
     * reset instancesCount and instance.
     */
    private static void reset()
    {
        instancesCount = 0;
        instance = null;
    }

    /**
     * validate system to run the test
     */
    private static void validate()
    {
        if (SingletonTest.PROCESSOR_COUNT < 2)
        {
            System.out.print("PROCESSOR_COUNT Must be >= 2 to Run the test.");
            System.exit(0);
        }
    }

    public static void main(String... args)
    {
        validate();
        System.out.printf("Summary :: PROCESSOR_COUNT %s, Running Test with %s of Threads. %n", PROCESSOR_COUNT, PROCESSOR_COUNT);
        
        long currentMili = System.currentTimeMillis();
        int testCount = 0;
        do
        {
            reset();

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                THREADS[i] = new Thread(SingletonTest::getInstance);

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                THREADS[i].start();

            for (int i = 0; i < PROCESSOR_COUNT; i++)
                try
                {
                    THREADS[i].join();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            testCount++;
        }
        while (instancesCount <= 1 && testCount < Integer.MAX_VALUE);
        
        System.out.printf("Singleton Pattern is broken after %d try. %nNumber of instances count is %d. %nTest duration %dms", testCount, instancesCount, System.currentTimeMillis() - currentMili);
    }
}

プログラムの出力は、getInstance asを使用してこれを処理するか、新しいSingletonTestを囲むロックをsynchronized追加する必要があることを明確に示しています。synchronized

概要::PROCESSOR_COUNT32、32個のスレッドでテストを実行しています。
133回試行するとシングルトンパターンが壊れます。
インスタンス数は30です。
テスト期間500ms
于 2020-08-30T07:51:15.033 に答える