前に何が起こるか?JMMの最も中心的な概念、読んだ後で理解できる

Happens-beforeは、JMMのコアコンセプトです。Javaプログラマーにとって、JVMを理解するには、発生前の理解鍵となります。

私の並行性シリーズでは、最初の3つの記事で、Java並行性メカニズムの基礎となる実装の3つの主要な要素であるvolatilesynchronized、およびatomicの操作について学びましたまた、Javaメモリモデルは、同時実行環境でのCPUキャッシュ、コンパイラ、およびプロセッサ命令の並べ替えによって引き起こされる可視性と順序付けの問題を解決するためのものです。その中でも、揮発性のメモリセマンティクスと、JMMの定義と実装方法の学習に焦点を当てました。揮発性メモリセマンティクスの実装原理を学習すると、命令の再配置に対するJMMのソリューションが実際に前に発生するルールを定義することを学びました。今日、垣間見るために、理解しやすい言葉で、誰よりも前に起こることを学ぶようにしてください。

Javaメモリモデルの学習に関するこれまでの記事をまだ読んでいない場合は、記事の最後に移動して、対応するリンクをクリックして読むことをお勧めします。

次に、テーマを入力して、今日のパフォーマンスを開始します。


JMMデザイン

先に起こることを学ぶために、まずJMMの設計意図を紹介します。この質問は現実から始まります。

  1. プログラマーがコードを作成するときは、メモリーモデルが理解しやすく、プログラムしやすいものである必要があるため、強力なメモリーモデルに依存してコーディングする必要があります。つまり、公理のように、明確に定義されたルールを使用して、ルールに従ってコードを記述します。
  2. コンパイラとプロセッサの実装では、制約をできるだけ少なくすることを望んでいます。結局のところ、それらを制限して実行効率に確実に影響を与え、パフォーマンスを提供するために最大限に最適化させることはできません。したがって、弱いメモリモデルが必要です。

上記の2つの点は明らかに矛盾しています。プログラマーとして、JMMが強力なメモリモデルを提供する一方で、基盤となるコンパイラとプロセッサがパフォーマンスを向上させるために弱いメモリモデルを必要とすることを期待しています。

コンピューターの分野では、メモリやCPUレジスターなど、多くのシナリオを比較検討する必要があります。CPUマルチレベルキャッシュは、パフォーマンスの問題を解決するために導入されていますが、マルチコアCPUの同時実行シナリオにもさまざまな問題をもたらします。したがって、ここでも、プログラマーのニーズを満たすバランスを見つけると同時に、パフォーマンスを最大化するためにコンパイラーとプロセッサーの制限を可能な限り満たす必要があります。

したがって、JMMは設計時に次の戦略を定義します。

  1. プログラムの実行結果を変更する並べ替えの場合、JMMはコンパイラとプロセッサにそのような並べ替えを禁止するよう要求します。
  2. プログラムの実行結果を変更しない並べ替えの場合、JMMはコンパイラとプロセッサを必要としません(JMMではこの並べ替えが可能です)。

このJMM設計図と組み合わせてそれを理解しましょう。

上の図からわかるように、JMMは十分なメモリ可視性保証をプログラマーに提供します。プログラムの実行結果に影響を与えない限り、次のプログラムAのような可視性保証は存在しません。 Bはプログラムの実行結果に影響を与えないため、保証しません。

double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C

これは別の側面につながります。コンパイラーとプロセッサーの制約をできるだけ満たすために、JMMはルールに従います。プログラムの実行結果が変更されない限り、コンパイラーとプロセッサーは必要なだけ最適化できます。たとえば、慎重に分析した後、ロックが単一のスレッドによってのみアクセス可能であるとコンパイラーが判断した場合、ロックを除去できます。別の例として、揮発性変数が注意深い分析の後で単一のスレッドによってのみアクセス可能であるとコンパイラーが判断した場合、コンパイラーは揮発性変数を通常の変数として扱うことができます。これらの最適化により、プログラムの実行結果が変わることはありませんが、プログラムの実行効率も向上します。

前に発生するルール

発生前ルールを理解するにはどうすればよいですか?文学的意味だけが最初に行われると理解されている場合、その逆が当てはまります。Happens-beforeは、前の操作が次の操作の前に発生したことを意味するのではなく、プログラマーのプログラミングの観点からはエラーはありませんが、実際には、前の操作の結果が後続の操作から見えることを表しています。の

これら2つのステートメントの違いは何ですか?

これは、JMMがプログラマーに提供するパースペクティブが順番に実行され、ある操作が別の操作の前に発生するため、最初の操作の実行結果が2番目の実行結果に表示され、最初の操作が実行の順序は2番目の順序の前です。これは、JMMがプログラマに保証するものあることに注意してください

しかし、実際には、前述のようにJMMがコンパイラーとプロセッサーに制約を課す場合、ルールは、プログラムの実行結果を変更せずにコンパイラーとプロセッサーがどのように最適化されるかに関係なくです。つまり、2つの操作の間に「前に発生」ルールがあり、Javaプラットフォームは、ルールで定義された順序で実行されるとは限りません。これは、プログラムの実行中にセマンティクスを変更できない限り、プログラマーが2つの操作の順序を変更するかどうかを気にしないためです。

appear-beforeの目的は、プログラム実行の結果を変更せずに、プログラム実行の並列性を可能な限り高めることです。

発生前の意味を理解した後、特定の発生前のルール定義を見てみましょう。

1.手続き順序規則

スレッドでは、プログラムシーケンスに従って、前の操作Happens-Beforeの後に後続の操作が続きますこれはまだ非常に理解しやすいものです。たとえば、上記の3行のコードでは、「double pi = 3.14;」は「double r = 1.0;」の最初の行で前に発生します。これはルール1の内容であり、シングルスレッドに沿っています。論理的思考、理解しやすい。

double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C

2.ロックルールを監視する

ロックを解除するには、前に発生してからロックをロックします。

このルールで言及されているロックは、実際にはJavaで同期されます。たとえば、次のコードは、同期ブロックに入る前に自動的にロックし、コードブロックの実行後に自動的にロックを解除します。ロックとロックの解除は、コンパイラーによって実装されます。

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁

したがって、ロックルールと組み合わせると、xの初期値が10であるとすると、スレッドAがコードブロックを実行するとxの値は12になり(ロックは実行後に自動的に解放されます)、スレッドBはスレッドがコードブロックに入ったときにスレッドを見ることができます。 xへの書き込み操作、つまりスレッドBはx == 12を参照できます。これは私たちの直感に沿ったものであり、非常に理解しやすいものです。

3.揮発性変数のルール

揮発性ドメインへの書き込みは、この揮発性ドメインの後続の読み取りの前に行われます

これは少し不可解です。揮発性変数への書き込み操作は、その後の揮発性変数への読み取り操作と比較して表示されます。これは、キャッシュが無効になっていることを意味します。1.5以前のバージョンのセマンティクスは変更されていないようです(前述)バージョン1.5より前では、揮発性変数と通常の変数間の順序変更が可能です)?このルールを単独で見ると本当ですが、ルール4を関連付けると、変化を感じることができます

4.推移性

AがBの前に発生し、BがCの前に発生する場合、AはCの前に発生します。

以下の例にルール4の推移性を適用します。

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

下の画像をご覧ください。

図から、次のことがわかります。

  1. "X = 42" Happens-Beforeは、ルール1の内容である変数 "v = true"を書き込みます。
  2. 変数「v = true」を書き込みます。発生する前に変数「v = true」を読み取ります。これがルール3の内容です。
  3. この推移的なルールに従って、次の結果が得られます: "x = 42" Happens-Beforeは変数 "v = true"を読み取ります。これは何を意味するのでしょうか?

スレッドBが「v = true」を読み取る場合、スレッドAによって設定された「x = 42」はスレッドBに表示されます。つまり、スレッドBは「x == 42」を見ることができますが、突然の実現感はありますか?これは、バージョン1.5の揮発性セマンティクスの拡張です。この拡張は非常に重要です。バージョン1.5の並行性ツールキット(java.util.concurrent)は、可視性を取得するために揮発性セマンティクスに依存しています。

5. start()ルール

これはスレッドの起動に関するものです。つまり、メインスレッドAが子スレッドBを開始した後、子スレッドBは、子スレッドBが開始される前にメインスレッドの動作を確認できます。

6. join()ルール

スレッドAがThreadB.join()オペレーションを実行して正常に戻る場合、スレッドBのオペレーションは、そのスレッドAがThreadB.join()オペレーションから正常に戻る前に発生します。

上記の6の発生前ルールの組み合わせにより、プログラマーに一貫したメモリの可視性を提供できます。一般的に使用されるルール1を他のルールと組み合わせて、並行プログラムを作成するための信頼できるメモリ可視性モデルを提供します。

総括する

Java言語では、Happens-Beforeのセマンティクスは本質的に一種の可視性です。AHappens-Before Bは、AイベントとBイベントが同じスレッドで発生するかどうかに関係なく、AイベントがBイベントに可視であることを意味します。たとえば、イベントAはスレッド1で発生し、イベントBはスレッド2で発生します。Happens-Beforeルールは、イベントAがスレッド2でも表示されることを保証します。

JMMの設計は2つの部分分かれています。1つプログラマ向けで、これは発生前ルールです。強力なメモリモデルを理解しやすい方法でプログラマに説明します。発生前ルールを理解するだけで済みます。並行処理セーフプログラムを作成します。もう1つの部分はJVM用に実装されています。コンパイラとプロセッサにできるだけ少ない制約を課してパフォーマンスを向上させるために、JMMはプログラムの実行結果に影響を与えずにそれを必要としません。つまり、最適化された並べ替えが可能です。前者のみに注意を払う必要があります。前者は、発生前ルールを理解することです。結局のところ、私たちはプログラマーであり、技術業界の専門知識を持っているので、安全な並行プログラムを作成できるといいですね。

 

おすすめの記事:


Javaでの並行性?最初に学習する必要がある同時実行メカニズムの基本的な実装の3つの原則

アリがインタビューしたとき、彼はJavaメモリモデルに落ちました

volatileキーワードがわかりませんか?急いで見てください

最近、ByteとBATにインタビューし、Javaコアテクノロジー、JVM、Java並行性、SSM、マイクロサービス、データベース、データ構造などをカバーするインタビュー資料「Java Interview BAT Clearance Manual」をまとめました。入手方法:気に入ったら、公式アカウントをフォローし、666に返信して受け取ると、次々とコンテンツが増えていきます

 

おすすめ

転載: blog.csdn.net/taurus_7c/article/details/105345315