全体的な紹介については、私が以前に書いたブログを参照してください:Java並行プログラミング(2):Javaメモリモデルと発生前のルール
全体的な構造も次のとおりです。
- JMMの紹介、抽象的な構造、並べ替え、シリアルのように、前に発生します。
- 同時に説明します:揮発性キーワード;
他のブロガーの高品質のブログも参照してください。Java同時実行性4を知る:Javaメモリモデル
次に、いくつかの追加を行います。
1.共有変数:もう一度
- インスタンスフィールド、静的フィールド、配列オブジェクト。ただし、ローカル変数とメソッドパラメータは含まれません。後者はスレッド専用であり、共有されないため、競合は発生しません。
2. Javaメモリモデルの8つの操作:
- ロック:メインメモリに作用する変数。変数をスレッドの排他的状態として識別します。
- Unlock(unlock):メインメモリに作用する変数。ロック状態の変数を解放し、解放された変数は他のスレッドによってロックできます。
- 読み取り:メインメモリに作用する変数。変数の値をメインメモリからスレッドの作業メモリに転送して、後続のロードアクションを実行します。
- ロード:作業メモリーに作用する変数。読み取り操作によってメインメモリーから取得された変数値を、作業メモリーの変数コピーに入れます。
- 使用:作業メモリーに作用する変数。作業メモリー内の変数の値を実行エンジンに渡します。この操作は、仮想マシンが変数の値を使用する必要のあるバイトコード命令を検出するたびに実行されます。
- 割り当て(割り当て):作業メモリーに作用する変数。実行エンジンから受け取った値を作業メモリー変数に割り当てます。この操作は、仮想マシンが変数に値を割り当てるバイトコード命令を検出するたびに実行されます。
- store:作業メモリーに作用する変数。作業メモリー内の変数の値を、後続の書き込み操作のためにメインメモリーに転送します。
- write:メインメモリに作用する変数。ストア操作でワーキングメモリから取得した変数の値をメインメモリ変数に入れます。
メインメモリからワーキングメモリに変数をコピーする場合:読み取り操作とロード操作を順番に実行する必要があります。
変数を作業メモリーからメインメモリーに同期して戻す場合は、ストア操作と書き込み操作を順番に実行する必要があります。
Javaメモリモデルでは、上記の2つの操作を順番に実行するだけでよく、連続して実行する必要があるという保証はありません。つまり、読み取りとロードの間、およびストアと書き込みの間に他の命令を挿入できます。たとえば、メモリ内の変数aとbにアクセスする場合、可能な順序は、読み取りa、読み取りb、ロードb、ロードaです。
さらに、Javaメモリモデルでは、上記の8つの操作を実行するときに次のルールを満たす必要があることも規定されています。
- 読み取りおよびロード、ストア、および書き込み操作の1つを個別に表示することはできません。つまり、変数をメインメモリから読み取ることはできず、単一の作業メモリは受け入れられません。または、からのライトバックが開始されます。ワーキングメモリですが、メインメモリは受け入れられません。
- スレッドは、最新の割り当て操作を破棄することはできません。つまり、作業メモリーで変数が変更された後、変更をメインメモリーに同期して戻す必要があります。
- スレッドは、理由もなく(割り当て操作が発生していない)、スレッドの作業メモリーからメインメモリーにデータを同期することを許可されていません。
- 新しい変数は、メインメモリでのみ「生成」できます。作業メモリで初期化されていない(ロードまたは割り当て)変数を直接使用することはできません。つまり、使用および保存操作を行う前に、最初に使用する必要があります。変数に対して実行されます。割り当ておよびロード操作が実行されました。
- 変数は同時に1つのスレッドでのみロックできますが、ロック操作は同じスレッドで複数回繰り返すことができます。ロックが複数回実行された後、変数はロック解除操作が同じように実行された場合にのみロック解除されます。何度か。
- 変数に対してロック操作を実行すると、作業メモリー内の2次変数の値がクリアされます。実行エンジンがこの変数を使用する前に、ロードを再実行するか、操作を割り当てて変数の値を初期化する必要があります。 。
- 変数が事前にロック操作によってロックされていない場合、ロック解除操作を実行することはできません。また、他のスレッドによってロックされている変数のロックを解除することもできません。
- 変数に対してロック解除操作を実行する前に、変数をメインメモリに同期して戻す必要があります(保存および書き込み操作を実行します)。
3.並べ替え:
並べ替えとは、プログラムのパフォーマンスを最適化するために、コンパイラとプロセッサが命令シーケンスを並べ替える手段を指します。
as-if-serialのセマンティクスとは、どのように並べ替えても(並列処理の程度を向上させるためのコンパイラとプロセッサ)、(シングルスレッド)プログラムの実行結果を変更できないことを意味します。コンパイラ、ランタイム、およびプロセッサはすべて、as-if-serialセマンティクスに準拠する必要があります。
as-if-serialセマンティクスに準拠するために、コンパイラとプロセッサは、データの依存関係がある操作を並べ替えません。これは、この並べ替えによって実行結果が変更されるためです。ただし、操作間にデータの依存関係がない場合、これらの操作はコンパイラーとプロセッサーによって並べ替えられる場合があります。
シングルスレッドプログラムでは、制御依存関係のある操作を並べ替えても実行結果は変更されません(これは、as-if-serialセマンティクスが制御依存関係のある操作の並べ替えを許可する理由でもあります)が、マルチスレッドプログラムでは、制御のある操作の並べ替え依存関係により、プログラムの実行結果が変わる場合があります。
4.発生前の線形発生の原則:
Javaメモリモデルの下でのいくつかの「自然な」先行関係。これらの先行関係は、シンクロナイザーの支援なしですでに存在しており、コーディングで直接使用できます。2つの操作の関係がこのリストになく、次のルールから導き出せない場合、それらは順序を保証せず、仮想マシンはそれらを自由に並べ替えることができます。
- プログラムシーケンスルール:スレッド内のすべての操作が発生します-スレッド内の後続の操作の前に。
- ロックルールの監視:ロックを解除するには、発生します-その前にロックをロックします。
- 揮発性変数のルール:揮発性ドメインへの書き込みが発生します-この揮発性ドメインの後続の読み取りの前。
- 推移性:Aが-Bの前に発生し、Bが-Cの前に発生する場合、Aは-Cの前に発生します。
- start()ルール:スレッドAが操作ThreadB.start()(スレッドBの開始)を実行する場合、スレッドAのThreadB.start()操作が発生します-スレッドBの操作の前に;
- join()のルール:スレッドAが操作ThreadB.join()を実行して正常に戻ると、スレッドBでの操作が発生します-そのスレッドAがThreadB.join()操作から正常に戻る前に。
- プログラム中断ルール:スレッドinterrupted()メソッドの呼び出しは、中断されたスレッドのコードの前に中断時間の発生を検出します。
- オブジェクトファイナライズルール:オブジェクトの初期化の完了(コンストラクターの実行の終了)は、それが発生するfinalize()メソッドの開始に先行します。
5.親和性、可視性、および順序:
Java並行プログラミング(4):3つの主要なプロパティの要約:原子性、秩序性、可視性
Javaメモリモデルは、同時実行プロセスで原子性、可視性、順序の3つの特性を処理する方法を中心に構築されています。
アトミシティ:
- Javaメモリモデルによって直接保証されるアトミック変数操作には、読み取り、ロード、割り当て、使用、保存、および書き込みが含まれます。基本的なデータ型のアクセスと読み取りはアトミック(非ロングおよびダブル)であると大まかに考えることができます。原子協定);
- アプリケーションシナリオでより広い範囲の原子性保証が必要な場合(頻繁に発生します)、仮想マシンはユーザーに対してロックおよびロック解除操作を直接開かないものの、Javaメモリモデルはこの要求を満たすためにロックおよびロック解除操作も提供します。これは、これら2つの操作を暗黙的に使用するための高レベルのバイトコード命令monitorenter および monitorexit を提供します。Javaコードに反映されるこれらの2つのバイトコード命令は、同期ブロック(synchronizedキーワード)です。したがって、同期されたブロック間の操作もアトミックです。
可視性:
- 可視性とは、スレッドが共有変数の値を変更すると、他のスレッドがその変更についてすぐに学習できることを意味します。
- Javaメモリモデルは可視性を実現します。Javaメモリモデルは、変数が変更された後、新しい値をメインメモリに同期し、変数が読み取られる前にメインメモリから変数値を更新します。これは、転送としてメインメモリに依存します。可視性を達成するための媒体。通常の変数であるか揮発性の変数であるかにかかわらず、性別。
- 通常の変数と揮発性変数の違いは次のとおりです。特別なルールvolatileは、メインメモリにすぐに同期される新しい値(編集)を保証し、使用前にメインメモリからすぐに更新するたびに。したがって、volatileはマルチスレッド操作中の変数の可視性を保証しますが、通常の変数はこれを保証できないと言えます。
- 揮発性に加えて、Javaには可視性を実現できる2つのキーワードがあり、それらは同期され、最終的なものです。
- 同期ブロックの可視性は、「変数に対してロック解除操作を実行する前に、変数をメインメモリに同期して戻す必要がある(ストアおよび書き込み操作を実行する)」というルールによって取得されます。
- finalキーワードの可視性は、次のことを意味します。最終的に変更されたフィールドはコンストラクターで一度初期化され、コンストラクターは「this」参照パスアウトをしませんでした(この参照エスケープは非常に危険なことです。他のスレッド「half -この参照を通じて初期化された "オブジェクト)、その後、最終フィールドの値は他のスレッドで見ることができます。
秩序:
- このスレッドで観察すると、すべての操作が正常に行われ、別のスレッドの別のスレッドで観察すると、すべての操作が順不同になります。
- 最初のフレーズは「シリアルセマンティクスの内部スレッドパフォーマンス」を意味し、最後の部分は「命令の並べ替え」現象と「ワーキングメモリとメインメモリの同期遅延」現象を意味します。
- Javaは、スレッド間の操作の順序を保証するために、volatileおよびsynchronizedの2つのキーワードを提供します。
- volatileキーワード自体には、命令の並べ替えを禁止するセマンティクスが含まれています。
- 同期は、「変数は同時に1つのスレッドによってのみロックできる」というルールによって取得されます。このルールは、同じロックを保持する2つの同期ブロックはシリアルにのみ入力できることを決定します。
6.揮発性キーワード:
変数が揮発性として定義されている場合、次の2つの特性があります。
- 1つ目は、すべてのスレッドに対するこの変数の可視性を確保することです。ここでの「可視性」とは、スレッドがこの変数の値を変更すると、新しい値が他のスレッドにすぐに認識されることを意味します。
- 揮発性変数は可視性のみを保証できるため、次の2つのルールを満たさないコンピューティングシナリオでは、原子性を確保するために(java.util.concurrentの同期クラスまたはアトミッククラスを使用して)ロックする必要があります。
- 操作の結果は、変数の現在の値に依存しません。または、単一のスレッドのみが変数の値を変更することを保証できます。
- 変数は、他の状態変数との不変制約に参加する必要はありません。
- 2つ目は、揮発性変数を使用する2つ目のセマンティクスは、命令の並べ替えの最適化を禁止することです。
- 通常の変数は、メソッドの実行中に割り当て結果に依存するすべての場所で正しい結果が得られることを保証するだけであり、変数割り当て操作の順序がプログラムコードの実行順序と一致することを保証することはできません。スレッドのメソッドの実行中にこれを認識することは不可能であるため、これはJavaメモリモデルで説明されているいわゆる「スレッド内のシリアルセマンティクス」です。
- 揮発性変数の読み取り操作のパフォーマンス消費は通常の変数のパフォーマンス消費とほぼ同じですが、プロセッサが異常に実行されないようにするためにネイティブコードに多くのメモリバリア命令を挿入する必要があるため、書き込み操作は遅くなる可能性があります。ただし、それでも、ほとんどのシナリオでの揮発性の総消費量は、ロックの消費量よりも少なくなります。揮発性とロックのどちらを選択するかについての唯一の根拠は、揮発性のセマンティクスが使用シナリオの要件を満たすことができるかどうかです。
揮発性の要約:
- 使用する前に、メインメモリから最新の値を更新して、他のスレッドによって作成された変数の変更された値を確認できるようにする必要があります。
- 作業メモリーでは、他のスレッドが変数に対する独自の変更を確認できるように、各変更の直後にメインメモリーに同期して戻す必要があります。
- volatileによって変更された変数は、コードの実行順序がプログラムの順序と同じになるように、命令の並べ替えによって最適化されません。