volatile キーワードの役割を深く理解する (3)

(3) Javaメモリモデル

メモリ モデルと同時プログラミングで発生する可能性のあるいくつかの問題について説明しました。Java メモリ モデルを見て、Java メモリ モデルが提供する保証と、マルチスレッド プログラミングを行う際にプログラム実行の正確性を保証するために Java で提供されるメソッドとメカニズムを検討してみましょう。

      Java 仮想マシン仕様では、Java プログラムがさまざまなプラットフォームで一貫したメモリ アクセスを実現できるように、さまざまなハードウェア プラットフォームやオペレーティング システムのメモリ アクセスの違いを保護する Java メモリ モデル (Java メモリ モデル、JMM) を定義する試みが行われています。 . 効果。では、Java メモリ モデルは何を規定するのでしょうか? これは、プログラム内の変数のアクセス ルールを定義し、より広い範囲でプログラムの実行順序を定義します。より良い実行パフォーマンスを得るために、Java メモリ モデルは、命令の実行速度を向上させるために実行エンジンがプロセッサのレジスタやキャッシュを使用することを制限したり、コンパイラが命令を並べ替えることを制限したりしないことに注意してください。つまり、Java メモリ モデルでは、キャッシュの一貫性と命令の並べ替えの問題も発生します。

     Java メモリ モデルでは、すべての変数がメイン メモリ (前述の物理メモリと同様) に格納され、各スレッドが独自の作業メモリ (以前のキャッシュと同様) を持つことが規定されています。スレッドによる変数の操作はすべて作業メモリ内で実行する必要があり、メインメモリ上で直接操作することはできません。また、各スレッドは他のスレッドの作業メモリにアクセスできません。

たとえば、次のような Java コードがあります。

i  = 10;

実行スレッドは、まず自身のワーカー スレッド内で変数 i が配置されているキャッシュ ラインに値を割り当ててから、その値をメイン メモリに書き込む必要があります。値 10 をメイン メモリに直接書き込む代わりに。

では、Java 言語自体は、アトミック性、可視性、順序付けに関してどのような保証を提供しているのでしょうか?

1. 原子性

Java では、基本データ型の変数の読み取りおよび代入操作はアトミックな操作です。つまり、これらの操作は、実行されても実行されなくても中断できません。

はい、もう一つ栗をください

x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

次に、上記のステートメントがアトミック操作であるかどうかを分析します。一見すると、上記の 4 つのステートメントの操作はすべてアトミック操作であると言う人もいるかもしれません。実際、ステートメント 1 だけがアトミック操作であり、他の 3 つのステートメントはアトミック操作ではありません。

ステートメント 1 は値 10 を x に直接割り当てます。これは、このステートメントを実行するスレッドが値 10 を作業メモリに直接書き込むことを意味します。

ステートメント 2には、実際には 2 つの操作が含まれています。最初に x の値を読み取り、次に x の値を作業メモリに書き込みます。x の値の読み取りと作業メモリへの x の値の書き込みの 2 つの操作はどちらもアトミックです操作ですが、これらを合わせてアトミックな操作ではありません。

同様に、x++ および x = x+1 には、x の値を読み取る、1 を加算する、新しい値を書き込むという 3 つの操作が含まれます。

したがって、上記の 4 つのステートメントでは、ステートメント 1 の操作のみがアトミックです。

言い換えれば、単純な読み取りと代入 (変数に数値を代入する必要があり、変数間の相互代入はアトミック操作ではありません) のみがアトミック操作です。(ただし、ここで注意点が1つあります。32ビットプラットフォームでは、64ビットデータの読み込みと代入は2回の操作で完了する必要があり、そのアトミック性は保証できません。ただし、最新のJDKでは、JVM は 64 ビット データの読み取りと割り当てもアトミック操作であることを保証しています)

上記のことからわかるように、Java メモリ モデルでは、基本的な読み取りと代入がアトミックな操作であることのみが保証されていますが、より広範囲の操作のアトミック性を実現したい場合は、synchronized と Lock を使用することで実現できます。synchronized と Lock は、常に 1 つのスレッドだけがコード ブロックを実行することを保証できるため、当然アトミック性の問題は発生せず、アトミック性が確保されます。

2. 可視性

可視性を確保するために、Java は可視性を確保するために volatile キーワードを提供します。共有変数が volatile によって変更されると、変更された値が直ちにメイン メモリに更新され、他のスレッドがその値を読み取る必要がある場合には、メモリに移動して新しい値を読み取ります。ただし、通常の共有変数は可視性を保証できません。通常の共有変数が変更された後、いつメインメモリに書き込まれるかが不確実であり、他のスレッドがそれらを読み取るときに、その時点ではメモリに元の古い値が残っている可能性があるため、可視性は保証できません。保証されます。

さらに、synchronized と Lock によって可視性も保証できます。Synchronized と Lock を使用すると、1 つのスレッドだけが同時にロックを取得して同期コードを実行することが保証され、変数の変更は前にメイン メモリに更新されます。ロックが解除されます。したがって、視認性は保証されます。

3. 秩序

Java メモリ モデルでは、コンパイラとプロセッサは命令を並べ替えることができますが、並べ替えプロセスはシングルスレッド プログラムの実行には影響しませんが、マルチスレッドの同時実行の正確さには影響します。

Java では、 volatile キーワードを使用して、特定の「順序」を確保できます (具体的な原則については次のセクションで説明します)。さらに、synchronized と Lock を使用して順序性を確保することもできます。明らかに、synchronized と Lock は、1 つのスレッドが各瞬間に同期コードを実行することを保証します。これは、スレッドに同期コードを順番に実行させるのと同じであり、自然に順序性を保証します。さらに、Java のメモリ モデルには、何らかの手段を講じなくても順序性が保証されるという、固有の「順序性」があり、これは一般に前発生原則と呼ばれます。2 つの操作の実行順序が前発生の原則から推定できない場合、それらの順序を保証することはできず、仮想マシンはそれらの順序を自由に変更できます。

以下に、事前発生の原則を詳しく紹介します。

  • プログラム順序規則: スレッド内では、コードの順序に従って、前に書かれた操作は後ろに書かれた操作より前に発生します。
  • ロック ルール: 同じロックのロック操作の前に、ロック解除操作が最初に発生します。
  • 揮発性変数のルール: 変数への書き込み操作は、後で変数の読み取り操作が実行される前に実行されます。
  • 転送ルール: 操作 A が操作 B の前に発生し、操作 B が操作 C の前に発生した場合、操作 A は操作 C の前に発生すると結論付けることができます。
  • スレッド開始ルール: Thread オブジェクトの start() メソッドは、このスレッドのすべてのアクションで最初に発生します。
  • スレッド割り込みルール: スレッド割り込み() メソッドの呼び出しは、割り込まれたスレッドのコードが割り込みイベントの発生を検出する前に行われます。
  • スレッド終了ルール: スレッド内のすべての操作はスレッドの終了検出前に発生し、Thread.join() メソッドの終了と Thread.isAlive() の戻り値によってスレッドが実行を終了したことを検出できます。
  • オブジェクトの終了ルール: オブジェクトの初期化の完了は、finalize() メソッドの開始前に発生します。

  これら 8 つの原則は、「Java 仮想マシンの徹底理解」から抜粋したものです。

最初の 4 つのルールを説明しましょう。

  プログラムの順序の規則については、プログラム コードの一部の実行は単一のスレッドで順序付けされているように見える、というのが私の理解です。このルールでは、「前に書かれた操作が後ろに書かれた操作の前に最初に発生する」と述べていますが、これは、プログラムが実行されるように見える順序でコードの順序で実行される必要があることに注意してください。仮想マシンはプログラム コード命令の並べ替えを実行する可能性があります。並べ替えは実​​行されますが、最終的な実行結果はプログラムの順次実行と一致しており、データ依存関係のない命令のみが並べ替えられます。したがって、単一スレッドでは、プログラムの実行は順番に実行されるように見えますが、これを注意深く理解する必要があります。実際、このルールはシングルスレッドでのプログラムの実行結果の正しさを保証するために使用されますが、マルチスレッドでのプログラムの実行の正しさは保証できません。

  2 番目のルールも理解しやすいです。つまり、シングル スレッドでもマルチスレッドでも、同じロックがロックされている場合は、ロック操作を続行する前にロックを解放する必要があります。

  3つ目のルールはより重要なルールであり、後述する内容でもあります。直感的に説明すると、スレッドが最初に変数を書き込み、次にスレッドがそれを読み取る場合、書き込み操作は必ず読み取り操作の前に行われるということです。

  4 番目のルールは実際には、前に発生する原則の推移性を反映しています。

参考文献: http://www.cnblogs.com/dolphin0520/

おすすめ

転載: blog.csdn.net/m0_37506254/article/details/82285139