42

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

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

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

私はそれらの意味を理解することができません。誰かが簡単な例で説明してくれると助かります。

4

4 に答える 4

42

最近の 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ます。

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

于 2013-04-27T06:29:27.647 に答える
8

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

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

最も単純な種類の事前発生関係は、同じスレッド内のアクション間で発生します。スレッド P での W から V への書き込みは、プログラムの順序に従って W が R の前に来ると仮定して、同じスレッドでの V の読み取り R の前に発生します。

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

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

于 2013-04-27T06:31:17.227 に答える
3

オラクルのドキュメントによると、彼らは、前発生関係は、ある特定のステートメントによるメモリ書き込みが別の特定のステートメントから見えることを単に保証するものであると定義しています。

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 の本体に表示されていることがわかります。

[Thread main] Finish count: 2は、スレッドの本体のすべての変更が、Thread.join() を呼び出すメイン スレッドに表示されることを示します。

明確に役立つことを願っています。

于 2014-09-05T08:07:33.647 に答える