序文
Java Concurrentプログラミングに精通している人なら誰でも、JAVAマルチスレッド操作の秩序と可視性を定義するJMM(Javaメモリモデル)の実現(HB)ルールがプログラムの結果に並べ替えの影響を妨げることを知っています。
Java言語には「前」のルールがあります。これは、Javaメモリモデルで定義されている2つの操作間の部分的な順序関係です。操作Aが最初に操作Bで発生した場合、操作Bが発生する前に、操作Aの影響は操作Bによって観察されることを意味します。「影響」には、メモリ内の共有変数の値の変更、メッセージの送信、通話メソッドなどが含まれます。この原則は特に重要です。これは、データに競争があるかどうか、スレッドが安全かどうかを判断するための主な根拠です。
公式声明によると:
変数が複数のスレッドで読み取られ、少なくとも1つのスレッドで書かれている場合、読み取り操作と書き込み操作の間にHB関係がない場合、データレースの問題が発生します。
Bを動作させるスレッドが操作Aの結果を確認するために(AとBが同じスレッドにあるかどうかに関係なく)、HBの原理はAとBの間で満たされなければならず、そうでない場合は、並べ替えにつながる可能性があります。
HB関係が欠落している場合、並べ替えの問題が発生する可能性があります。
HBのルールは何ですか?
誰もがこれに非常に精通しています。ほとんどの本や記事が紹介されます。ここで簡単に確認しましょう:
その中で、私は配信ルールを太字にしましたが、これは非常に重要です。配信ルールを習熟させる方法は、同期を達成するための鍵です。
次に、HBを別の観点から説明します。操作A HBがBを操作する場合、共有変数の操作Aの操作結果が操作Bに表示されます。
同時に、B HB操作がCを操作する場合、共有変数の操作Aの動作結果がB操作Bに表示されます。
可視性を達成する原則は、キャッシュプロトコルとメモリバリアです。可視性は、キャッシュコヒーレンスプロトコルとメモリの障壁を通じて達成されます。
同期を実現する方法は?
Doug Leaの著書「Java Concurrency in Practice」では、次の説明は次のとおりです。
この本には、HBのいくつかのルールを組み合わせることで、ロック解除された保護された変数の可視性を実現できます。
しかし、この手法はステートメントの順序に敏感であるため、エラーが発生しやすいためです。
次に、著者は、揮発性ルールとプログラム順序ルールを介して変数を同期する方法を示します。
おなじみの例を持ってみましょう:
class threadprintdemo {static int num = 0;静的揮発性ブールフラグ= false; public static void main(string [] args){thread t1 = new thread(() - > {for(; 100> num;){if(!flag &&(num == 0 || ++ num%2 == 0)){system.out.println(num); flag = true;}}}});スレッドt2 = newスレッド(() - > {for(; 100> num;){if(flag &&(++ num%2!= 0)){system.out.println(num); flag = false;}}}); t1.start(); t2.start(); }}このコードの目的は、2つのスレッド間で番号0〜100を印刷することです。
同時プログラミングに精通している学生は、このnum変数は揮発性を使用しておらず、視界の問題があると言わなければなりません。つまり、T1スレッドはnumを更新し、T2スレッドはそれを知覚できません。
ハハ、著者は最初はそう考えていましたが、最近HBルールの研究を通じて、numの揮発性の変更を削除することは問題ないことがわかりました。
分析しましょう。ポスターは写真を描きました。
この数字を分析しましょう。
注: HBルールは、以前の操作の結果が次の操作に表示されることを保証します。
したがって、上記のアプレットでは、スレッドBは、揮発性で修正されていない場合でも、スレッドAによるnumの変更を完全に認識しています。
このようにして、HBの原則を使用して、変数の同期動作を実現します。つまり、マルチスレッド環境では、共有変数の同時変更のセキュリティを確保します。また、この変数にはJavaプリミティブがありません:揮発性および同期およびCAS(カウントされると仮定)。
これは安全ではないように見えるかもしれません(実際には安全)、理解しやすいようにはないかもしれません。これはすべて、基礎となるHBのキャッシュプロトコルとメモリバリアによって実装されるためです。
同期を実現する他のルール
スレッド終端ルールを使用した実装:
static int a = 1; public static void main(string [] args){thread tb = new thread(() - > {a = 2;}); thread ta = new thread(() - > {try {tb.join();} catch(interruptedexception e){// no} system.out.println(a);}); ta.start(); tb.start(); }スレッド開始ルールを使用して実装してください。
static int a = 1; public static void main(string [] args){thread tb = new thread(() - > {system.out.println(a);}); thread ta = new thread(() - > {tb.start(); a = 2;}); ta.start(); }これらの2つの操作は、変数aの可視性を保証することもできます。
それは本当に以前の概念を破壊します。以前の概念では、変数が揮発性または最終的に変更されていない場合、マルチスレッドの下での読み取りと執筆は間違いなく安全ではありません。
ただし、HBを使用することにより、達成できます。
要約します
この記事のタイトルは、以前には共有変数の同期操作を実現することですが、主な目的は、より深く実現することです。その前の概念を理解することは、実際に次の操作に対する前の操作の秩序性を確保することと、操作の可視性により、マルチスレッド環境になります。
同時に、推移的ルールを柔軟に使用してからルールを組み合わせることにより、2つのスレッドを同期させることができます。プリミティブを使用せずに指定された共有変数の実装も視認性を確保できます。これは読みやすいものではないようですが、試みでもあります。
Doug Leaは、同期を実現するためにルールを組み合わせる方法についてJUCで練習をします。
たとえば、futureTaskの古いバージョンの内部クラスの同期(消滅)は、tryReleaseSharedメソッドを介して揮発性変数を変更し、tryAcquiresharedは揮発性ルールを利用する揮発性変数を読み取ります。
これにより、TryReleaseSharedの前に不揮発性結果変数を設定し、TryAcquireshared後に結果変数を読み取ることにより、プログラムの順序ルールを利用します。
これにより、結果変数の可視性が保証されます。最初の例と同様に、プログラムの注文ルールと揮発性ルールを使用して、通常の可視性を実現します。
Doug Lea自身は、この「ヘルプの使用」テクノロジーは非常にエラーが発生しやすく、注意して使用する必要があると述べました。しかし、場合によっては、この種の「レバレッジ」は非常に合理的です。
実際、BlockingQueueは、以前に「使用される」こともあります。ロック解除ルールを覚えていますか?ロック解除が発生した場合、内部要素が表示されなければなりません。
クラスライブラリには、偶発的な原則を「使用」する他の操作があります。コンカレントコンテナ、カウントダウンラッチ、セマフォ、将来、執行者、サイクリックバリア、交換器など。
要するに、要するに:
たまたま原則はJMMの中核です。 HBの原則が満たされた場合にのみ、注文し、可視性を確保できます。そうしないと、コンパイラがコードを並べ替えます。 HBは、ロックと揮発性のルールを定義しています。
HBルールの適切な組み合わせにより、通常の共有変数の正しい使用を実現できます。
さて、上記はこの記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。