8

この件に関していくつか質問がありますが、質問の意図ではないため、ほとんどはこの問題を回避します。

クラスに static volatile がある場合:

private static volatile MyObj obj = null;

そして、以下の方法で私は:

public MyObj getMyObj() {
    if (obj == null) {
        obj = new MyObj();// costly initialisation
    }
    return obj;
}

obj == null1 つのスレッドだけがフィールドに書き込むように同期する必要がありますか?それとも、条件を評価する他のスレッドが書き込みをすぐに確認できるようにする必要がありますか?

別の言い方をすれば、volatile を使用すると、静的変数への書き込みへのアクセスを同期する必要がなくなりますか?

4

5 に答える 5

8

1 つのスレッドだけがフィールドに書き込むようにするには、何らかのロック必要です。揮発性に関係なく、2 つのスレッドはどちらもobjnull を「認識」し、現在のコードで初期化を開始できます。

個人的には、次の 3 つのオプションのいずれかを選択します。

  • クラスのロード時に初期化します (これは遅延になることはわかっていますが、最初に呼び出されるまで待機するほど遅延はありませんgetMyObj):

    private static final MyObj obj = new MyObj();
    
  • 無条件ロックを使用します。

    private static MyObj obj;
    private static final Object objLock = new Object();
    
    public static MyObj getMyObj() {
        synchronized(objLock) {
            if (obj == null) {
                obj = new MyObj();
            }
            return obj;
        }
    }
    
  • そのように怠惰のためにネストされたクラスを使用します。

    public static MyObj getMyObj() {
        return MyObjHolder.obj;
    }
    
    private static class MyObjHolder {
        static final MyObj obj = new MyObj();
    }
    
于 2012-05-22T07:22:30.260 に答える
3

はい、絶対に同期する必要があります(またはSingleton Holder idiomのようなより良いイディオムを使用してください)。そうしないと、複数のスレッドがオブジェクトを複数回初期化する (その後、異なるインスタンスを使用する) 危険性があります。

次のような一連のイベントを考えてみましょう。

  1. スレッド A が入っgetMyObj()て、obj == null
  2. スレッド B が入っgetMyObj()て、obj == null
  3. スレッド A は a を構築しますnew MyObj()- それを呼びましょうobjA
  4. スレッド B は a を構築しますnew MyObj()- それを呼びましょうobjB
  5. スレッド A が割り当てobjAますobj
  6. スレッド B はに割り当てobjBます(この時点ではもうobj存在しないため、スレッド A によって割り当てられた への参照は上書きされます)nullobjA
  7. スレッド A が終了getMyObj()し、使用を開始するobjA
  8. スレッド B が終了getMyObj()し、使用を開始しますobjB

このシナリオは、任意の数のスレッドで発生する可能性があります。ここでは、簡単にするために、イベントの厳密な順序付けを想定していますが、実際のマルチスレッド環境では、イベント 1-2、3-4、および/または 7-8 は、終了を変更することなく、部分的または完全に時間的に重複する可能性があることに注意してください。結果。

ホルダーイディオムの例:

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        public static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

INSTANCEこれはそのままで安全であることが保証されていますfinalfinalJava メモリ モデルは、含まれているクラスをロードする際に、フィールドが初期化され、任意の数のスレッドに対して正しく表示されることを保証します。LazyHolderとはprivateによってのみ参照されるため、が最初に呼び出さgetInstance()れたときにのみロードされます。getInstance()その時点でINSTANCE、バックグラウンドで初期化され、JVM によって安全に公開されます。

于 2012-05-22T07:21:51.663 に答える
1

いいえ、アクセスを同期する必要があります。volatileあるスレッドによって変数に加えられた変更を、他のスレッドが見ることができるようにします。

次の実行フローを想像してください (2 つのスレッド T1 と T2 を想定)。

  1. obj は最初は null です。
  2. T1: if (obj == null): はい
  3. T2: if (obj == null): はい
  4. T1: MyObj の新しいインスタンスを作成し、それを obj に割り当てます
  5. T2: MyObj の新しいインスタンスも作成し、obj に割り当てます。

一度だけ作成されると思っていたオブジェクトを 2 回作成します。そして、これは悪いシナリオではありません。変数にも割り当てられていないオブジェクトを返すことになる可能性があります。

于 2012-05-22T07:23:43.523 に答える
0

これを処理するさらに別の方法は、ダブルチェックです (注: Java 5 以降でのみ機能します。詳細については、こちらを参照してください)。

public class DoubleCheckingSingletonExample
{
    private static final Object lockObj = new Object();

    private static volatile DoubleCheckingSingletonExample instance;

    private DoubleCheckingSingletonExample()
    {
        //Initialization
    }

    public static DoubleCheckingSingletonExample getInstance()
    {
        if(instance == null)
        {
            synchronized(lockObj)
            {
                if(instance == null)
                {
                    instance = new DoubleCheckingSingletonExample();
                }
            }
        }

        return instance;
    }
}

getInstance が 2 つのスレッドから同時に呼び出されると、両方とも最初にインスタンスを null として認識し、別のスレッドが同期ブロックに入り、オブジェクトをインスタンス化します。もう 1 つは、インスタンスが null ではなくなったことを確認し、インスタンス化を試みません。getInstance をさらに呼び出すと、インスタンスが null ではないことがわかり、ロックしようとしなくなります。

于 2012-05-22T07:29:47.450 に答える
0

そのコードはスレッドセーフではありません。複数のスレッドが関数を実行すると、MyObj の複数のインスタンスが作成される可能性があります。ここでは何らかの形式の同期が必要です。

基本的な問題は、このブロック コード:

if (obj == null) {
     obj = new MyObj();// costly initialisation
}

アトミックではありません。実際、それがアトミックになるには非常に長い道のりです。

于 2012-05-22T07:22:23.193 に答える