23

私の質問はこれの拡張です:揮発性の保証と順不同の実行

より具体的にするために、初期化後に 2 つの状態になる単純なクラスがあるとします。

class A {
    private /*volatile?*/ boolean state;
    private volatile boolean initialized = false;

    boolean getState(){
        if (!initialized){
            throw new IllegalStateException();
        }
        return state;
    }

    void setState(boolean newState){
        state = newState;
        initialized = true;
    }
}

初期化されたフィールドはvolatileと宣言されているため、事前発生の「バリア」が導入され、並べ替えが行われないことが保証されます。状態フィールドは、初期化されたフィールドが書き込まれる前に のみ書き込まれ、初期化されたフィールドが読み取られたにのみ読み取られるため、状態の宣言からvolatileキーワードを削除しても、古い値が表示されることはありません。質問は次のとおりです。

  1. この推論は正しいですか?
  2. 初期化されたフィールドへの書き込みが最適化されず (初回のみ変更されるため)、「バリア」が失われないことが保証されていますか?
  3. フラグの代わりに、次のようにCountDownLatchが初期化子として使用されたとします。

    class A {
        private /*volatile?*/ boolean state;
        private final CountDownLatch initialized = new CountDownLatch(1);
    
        boolean getState() throws InterruptedException {
            initialized.await();
            return state;
        }
    
        void setState(boolean newState){
            state = newState;
            initialized.countdown();
        }
    }
    

    それでもよろしいでしょうか?

4

2 に答える 2

8

あなたのコードは(ほとんど)正しく、それは一般的なイディオムです。

// reproducing your code
class A

    state=false;              //A
    initialized=false;        //B

    boolean state;
    volatile boolean initialized = false;        //0

    void setState(boolean newState)
        state = newState;                        //1
        initialized = true;                      //2

    boolean getState()
        if (!initialized)                        //3
            throw ...;
        return state;                            //4

行#A#Bは、デフォルト値を変数に書き込むための擬似コードです(フィールドのゼロ化とも呼ばれます)。それらを厳密な分析に含める必要があります。#Bは#0とは異なることに注意してください。両方が実行されます。行#Bは、揮発性の書き込みとは見なされません。

すべての変数に対するすべての揮発性アクセス(読み取り/書き込み)は、全順序で行われます。#4に到達した場合、この順序で#2が#3の前にあることを確認します。

initialized#B、#0、#2への3つの書き込みがあります。#2のみがtrueを割り当てます。したがって、#2が#3の後にある場合、#3はtrueを読み取ることができません(これはおそらく、私が完全に理解していない空中保証がないためです)、#4に到達できません。

したがって、#4に到達した場合、#2は#3の前にある必要があります(揮発性アクセスの全順序)。

したがって、#2が発生します- #3の前に(揮発性の書き込みが発生します-後続の揮発性の読み取りの前に)。

プログラミングの順序により、#1は#2の前に、#3は#4の前に発生します。

したがって、推移性によって、#1が発生します-#4の前に。

Line#A、デフォルトの書き込みが発生します-すべての前に(他のデフォルトの書き込みを除く)

したがって、変数へのすべてのアクセスstateは、発生前のチェーンにあります:#A->#1->#4。データの競合はありません。プログラムは正しく同期されています。読み取り#4は書き込み#1を遵守する必要があります

ただし、少し問題があります。#Bにはすでにfalseが割り当てられているため、行#0は明らかに冗長です。実際には、揮発性の書き込みはパフォーマンス上無視できないため、#0は避ける必要があります。

さらに悪いことに、#0の存在は、望ましくない動作を引き起こす可能性があります。#0は#2の後に発生する可能性があります。setState()したがって、呼び出されることが発生する可能性がありますが、その後getState()はエラーがスローされ続けます。

これは、オブジェクトが安全に公開されていない場合に可能です。スレッドT1がオブジェクトを作成し、それを公開するとします。スレッドT2はオブジェクトを取得し、それを呼び出しますsetState()。パブリケーションが安全でない場合、T1がオブジェクトの初期化を完了する前に、T2はオブジェクトへの参照を監視できます。

Aすべてのオブジェクトを安全に公開する必要がある場合は、この問題を無視できます。それは合理的な要件です。それは暗黙のうちに期待することができます。

ただし、行#0がない場合、これはまったく問題になりません。デフォルトの書き込み#Bが発生する必要があります-#2の前に、したがって、が呼び出される限り、setState()後続のすべての書き込みgetState()は監視されinitialized==trueます。

カウントダウンラッチの例では、initializedfinal;です。これは、安全な公開を保証するために重要です。すべてのスレッドは、適切に初期化されたラッチを監視します。

于 2011-11-09T17:01:00.987 に答える
-1

1. この推論は正しいですか?

いいえ、状態はスレッドにキャッシュされるため、最新の値を取得することはできません。

2. 初期化されたフィールドへの書き込みが最適化されず (初回のみ変更されるため)、「バリア」が失われないことが保証されていますか?

はい

3. フラグの代わりに、CountDownLatch が次のように初期化子として使用されたとします...

@ratchet フリークが述べたように、 CountDownLatch は 1 回限りのラッチですが、volatileは再利用可能なラッチのようなものので、3 番目の質問に対する答えは次のようになります。

于 2011-11-09T11:37:17.820 に答える