web-dev-qa-db-ja.com

メモリの一貫性-Java

読みながらJavaメモリー整合性エラーに関するドキュメント。関係を作成する前に発生する2つのアクションに関連するポイントを見つけました:

  • ステートメントがThread.start()を呼び出すと、そのステートメントとの発生前関係を持つすべてのステートメントは、新しいスレッドによって実行されるすべてのステートメントと発生前関係も持ちます。新しいスレッドの作成につながったコードの影響は、新しいスレッドに表示されます。

  • スレッドが終了して別のスレッドのThread.join()が戻ると、終了したスレッドによって実行されたすべてのステートメント
    スレッドには、すべてのステートメントと前に発生する関係があります
    参加が成功した後。スレッド内のコードの効果は、結合を実行したスレッドに表示されます。

私には彼らの意味が理解できません。誰かが簡単な例でそれを説明するのは素晴らしいことです。

33
Prateek

最近のCPUは、たとえば疑似コードを実行した場合など、更新された順序でデータを常にメモリに書き込むわけではありません(変数は常に単純化のためにここにメモリに格納されていると仮定しています)。

a = 1
b = a + 1

... CPUは、bをメモリに書き込む前に、aをメモリに書き込む可能性があります。上記のコードを実行しているスレッドは、割り当てが行われると、どちらの変数の古い値も決して見ないので、単一のスレッドで実行している限り、これは実際には問題になりません。

マルチスレッドは別の問題です。次のコードを使用すると、別のスレッドが重い計算の値を取得できるようになります。

a = heavy_computation()
b = DONE

...他のスレッドが...

repeat while b != DONE
    nothing

result = a

しかし問題は、結果がメモリに格納される前に完了フラグがメモリに設定される可能性があるため、他のスレッドが計算結果が書き込まれる前にメモリアドレスaの値を取得する可能性があることですメモリ

同じ問題が-Thread.startThread.joinに「前に発生した」保証がない場合-次の問題が発生しますのようなコード;

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

...aには、スレッドの開始時にメモリに値が格納されていない場合があるためです。

ほとんどの場合、新しいスレッドでは、開始する前に初期化したデータを使用できるようにする必要があるため、Thread.startには「前に発生」という保証、つまり更新されたデータがありますThread.startを呼び出す前に、新しいスレッドで使用できることが保証されています。同じことがThread.joinにも当てはまります。ここで、新しいスレッドによって書き込まれたデータは、終了後にそれに参加するスレッドから見えることが保証されています

単にスレッド化がはるかに簡単になります。

33

このことを考慮:

_static int x = 0;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            int y = x;
        };
    };
    t.start();
}
_

メインスレッドはフィールドxを変更しました。 Javaメモリモデルは、メインスレッドと同期されていない場合、この変更が他のスレッドに表示されることを保証しません。ただし、スレッドtは、メインスレッドがt.start()が呼び出され、JLSはt.start()を呼び出すとxへの変更がt.run()に表示されることを保証するため、yは_1_が割り当てられています。

同じことがThread.join();

21

スレッドの可視性の問題は、Javaメモリモデルに従って正しく同期されないコードで発生する可能性があります。コンパイラとハードウェアの最適化により、1つのスレッドによる書き込みは行われません。 Javaメモリモデルは正式なモデルであり、プログラマがスレッドの可視性の問題を回避できるように、「適切に同期される」というルールを明確にします。

Happens-beforeはそのモデルで定義された関係であり、特定の実行を参照します。 happens-beforeであることが証明されている書き込みW読み取りRは、他の干渉する書き込みがない(つまり、読み取りとの発生前の関係がないもの)と想定して、その読み取りによって確実に表示されます。またはその関係に従ってそれらの間で起こっているもの)。

最も単純な種類の前に発生する関係は、同じスレッド内のアクション間で発生します。スレッドPのVへのWの書き込みは、同じスレッドでVの読み取りRの前に発生します。

あなたが参照しているテキストは、thread.start()とthread.join()も発生前の関係を保証すると述べています。 thread.start()の前に発生するアクションは、そのスレッド内のアクションの前にも発生します。同様に、スレッド内のアクションは、thread.join()の後に表示されるアクションの前に発生します。

それの実際的な意味は何ですか?たとえば、スレッドを開始して、スレッドが安全でない方法で終了するのを待つ場合(たとえば、長時間のスリープ、またはいくつかの非同期フラグのテスト)、次に、スレッドでは、それらが部分的に表示される場合があり、データの不整合のリスクがあります。 join()メソッドは、スレッドによって公開されたデータの一部が他のスレッドによって完全かつ一貫して可視であることを保証するバリアとして機能します。

8
Eyal Schneider

Oracleのドキュメントによると、彼らは「発生前の関係」は、ある特定のステートメントによるmemory writesが別の特定のステートメントに対するvisibleであることを保証するだけであると定義しています。

package happen.before;

public class HappenBeforeRelationship {


    private static int counter = 0;

    private static void threadPrintMessage(String msg){
        System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {

        threadPrintMessage("Increase counter: " + ++counter);
        Thread t = new Thread(new CounterRunnable());
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            threadPrintMessage("Counter is interrupted");
        }
        threadPrintMessage("Finish count: " + counter);
    }

    private static class CounterRunnable implements Runnable {

        @Override
        public void run() {
            threadPrintMessage("start count: " + counter);
            counter++;
            threadPrintMessage("stop count: " + counter);
        }

    }
}

出力は次のようになります。

[Thread main] Increase counter: 1
[Thread Thread-0] start count: 1
[Thread Thread-0] stop count: 2
[Thread main] Finish count: 2

出力を見て、行[Thread Thread-0] start count:1は、Thread.start()の呼び出し前のすべてのカウンターの変更がスレッドの本体。

そして、行[Thread main] Finish count:2は、Thread.body()を呼び出すメインスレッドがThreadの本文のすべての変更を認識できることを示しています。

それがあなたを明確に助けることを願っています。

1
Ken Block