JMMと呼ばれるJavaメモリモデルは、メモリの可視性のために無関係な特定のプラットフォームと、開発者が提供するマルチスレッド環境で並べ替えることができるかどうか、一連のJava仮想マシンプラットフォームの統一保証です。 (ヒープ、メソッド領域、スレッドスタックなどのメモリ領域を指すJavaランタイムの用語とメモリ分布の観点からはあいまいな場合があります。
同時プログラミングには多くのスタイルがあります。 CSP(通信シーケンシャルプロセス)、アクター、およびその他のモデルに加えて、最もよく知られているモデルは、スレッドとロックに基づいた共有メモリモデルである必要があります。マルチスレッドプログラミングでは、3種類の並行性の問題に注意する必要があります。
・原子性・可視性・並べ替え
原子性には、他のスレッドが中間状態を確認できるか、スレッドが複合操作を実行するときに干渉するかどうかが含まれます。通常、それはI ++の問題です。 2つのスレッドは、共有ヒープメモリで++操作を同時に実行します。 JVM、ランタイム、およびCPUでの++操作の実装は、複合操作である可能性があります。たとえば、JVMの指示の観点からすると、ヒープメモリからオペランドスタックまでのIの値を読み取り、1つを追加し、ヒープメモリiに書き戻すことです。これらの操作中に、正しい同期がない場合、他のスレッドも同時に実行することができ、それがデータの損失やその他の問題につながる可能性があります。競争状態とも呼ばれる一般的な原子性の問題は、Read-Modify-Writeなどの故障結果に基づいて判断されます。視界と並べ替えの問題はどちらもシステムの最適化に起因します。
CPUの実行速度とメモリのアクセス速度は、時間の局所性や空間局位などのローカリゼーション原理に基づいてパフォーマンスを最適化するために、真剣に不一致であるため、CPUはメモリ間に多層キャッシュを追加しました。データを取得する必要がある場合、CPUは最初にキャッシュに移動して、対応するキャッシュが存在するかどうかを調べます。存在する場合、直接返されます。それが存在しない場合、それはメモリにフェッチされ、キャッシュに保存されます。現在、より多くのマルチコアプロセッサが標準になっているようになったため、各プロセッサには独自のキャッシュがあり、キャッシュの一貫性の問題が含まれます。 CPUには、さまざまな長所と短所の一貫性モデルがあります。最も強い一貫性は最も高いセキュリティであり、シーケンシャル思考モードにも準拠しています。ただし、パフォーマンスの面では、異なるCPU間の調整された通信が必要であるため、多くのオーバーヘッドがあります。
典型的なCPUキャッシュ構造図は次のとおりです
CPUの命令サイクルは、通常、命令フェッチ、データを読み取るための命令の解析、命令の実行、およびレジスタまたはメモリにデータを書き戻します。シリアルで命令を実行すると、読み取りデータと保存されたデータは長時間かかります。そのため、CPUは通常、命令パイプラインを使用して、工場パイプラインのように全体的なスループットを改善するために複数の命令を実行します。
データの読み取り速度とメモリへのデータを書き戻す速度は、命令を実行するのと同じ桁ではないため、CPUはレジスタとキャッシュをキャッシュとバッファーとして使用します。メモリからデータを読み取ると、キャッシュラインが読み取られます(ディスクの読み取りとブロックの読み取りに似ています)。データを書き戻すモジュールは、古いデータがキャッシュにない場合、命令サイクルの次の段階を実行し続ける場合、ストレージリクエストをストアバッファーに入れます。キャッシュに存在する場合、キャッシュが更新され、キャッシュ内のデータは特定のポリシーに従ってメモリに洗い流されます。
パブリッククラスメモリモデル{private int count;プライベートブールストップ; public void initcountandstop(){count = 1; stop = false; } public void doloop(){while(!stop){count ++; }} public void printresult(){system.out.println(count); System.out.println(stop); }}上記のコードを実行すると、stop = falseの前にcount = 1が実行されると思われる場合があります。これは、上記のCPU実行図に示されている理想的な状態では正しいものですが、レジスタとキャッシュバッファリングを検討する場合は間違っています。たとえば、停止自体はキャッシュにありますが、カウントはそこにありません。その後、停止が更新され、カウント書き込みバッファーがメモリにリフレッシュされます。
さらに、CPUとコンパイラ(通常はJITを参照)は、命令実行順序を変更する場合があります。たとえば、上記のコードでは、count = 1およびstop = falseには依存関係がないため、CPUとコンパイラはこれら2つの順序を変更できます。シングルスレッドプログラムの見解では、結果は同じです。これはまた、CPUとコンパイラが確保する必要があるAS-if-Serialです(実行命令の変更方法に関係なく、シングルスレッドの実行結果は変更されません)。プログラムの実行のほとんどはシングルスレッドであるため、このような最適化は受け入れられ、パフォーマンスの向上が大きくなります。ただし、マルチスレッドの場合、必要な同期操作なしで予期しない結果が発生する場合があります。たとえば、スレッドT1がinitcountandStopメソッドを実行した後、スレッドT2はprintresultを実行します。これは0、false、1、false、または0、trueです。スレッドT1がdoloop()を最初に実行し、スレッドT2がinitcountandStopを1秒実行する場合、T1はループから飛び出すことができます。または、コンパイラの最適化により停止の変更が表示されない場合があります。
上記のマルチスレッドの状況ではさまざまな問題のため、マルチスレッドのプログラムシーケンスは実行順序ではなくなり、根本的なメカニズムになります。プログラミング言語は、開発者に保証を与える必要があります。簡単に言えば、この保証は、スレッドの変更が他のスレッドに表示される場合です。したがって、Java言語は、JavamemoryModel、つまりJavaメモリモデルを提案します。これには、このモデルの規則に従って実装が必要です。 Javaは、開発者がすべてのプロセッサプラットフォームでマルチスレッドプログラムの正しさを確保できるように、揮発性、同期、最終などのメカニズムを提供します。
JDK1.5の前に、Javaのメモリモデルには深刻な問題がありました。たとえば、古いメモリモデルでは、コンストラクターが完了した後にスレッドが最終フィールドのデフォルト値を表示する場合があり、揮発性フィールドの書き込みは、不揮発性フィールドの読み取りと書き込みで再注文される場合があります。
したがって、JDK1.5では、以前の問題を修正するためにJSR133を通じて新しいメモリモデルが提案されました。
ルールを並べ替えます
揮発性およびモニターロック
| 再注文することは可能ですか? | 2番目の操作 | 2番目の操作 | 2番目の操作 |
|---|---|---|---|
| 最初の操作 | 通常の読書/普通の執筆 | 揮発性読み取り/モニターENTER | 揮発性書き込み/モニターの出口 |
| 通常の読書/普通の執筆 | いいえ | ||
| voaltile読み取り/モニターENTER | いいえ | いいえ | いいえ |
| 揮発性書き込み/モニターの出口 | いいえ | いいえ |
通常の読み取りとは、GetField、GetStatic、および非揮発性アレイの配列を指し、通常の読み取りは、Putfield、Putstatic、および非揮発性アレイの配列を指します。
揮発性フィールドの読み取りと書き込みは、それぞれGetfield、GetStatic、Putfield、Putstaticです。
MonitorEnterは、同期ブロックまたは同期方法を入力することであり、Monitorexistは同期ブロックまたは同期方法の除外を指します。
上記の表では、並べ替えを許可しない2つの操作を指します。たとえば、(通常の執筆、揮発性の執筆)は、不揮発性フィールドの並べ替えと、その後の揮発性フィールドの書き込みの並べ替えを指します。ノーがない場合、それは並べ替えが許可されていることを意味しますが、JVMは最小限のセキュリティを確保する必要があります - 読み取り値はデフォルト値であるか、他のスレッドによって書き込まれます(64ビットのダブルおよびロングリードおよび書き込み操作は特別なケースです。
最終フィールド
最終フィールドには2つの特別なルールがあります
最終フィールドの書き込み(コンストラクター内)も、最終フィールドオブジェクト自体の参照の書き込みも、最終フィールドを保持しているオブジェクトの後続の書き込み(コンストラクターの外側)で並べ替えることはできません。たとえば、次のステートメントを並べ替えることはできません
X.FinalField = V; ...; sharedref = x;
最終フィールドの最初の負荷は、最終フィールドを保持しているオブジェクトの書き込みで並べ替えることはできません。たとえば、次のステートメントでは、並べ替えが許可されていません。
x = sharedref; ...; i = x.finalfield
メモリバリア
プロセッサはすべて、特定のメモリの障壁またはフェンスをサポートして、異なるプロセッサ間の並べ替えとデータの可視性を制御します。たとえば、CPUがデータを書き戻すと、ストアリクエストを書き込みバッファーに入れて、メモリに洗い流すのを待ちます。このストアリクエストは、データの可視性を確保するために障壁を挿入することにより、他のリクエストで並べ替えることを防ぐことができます。ライフの例を使用して、障壁を比較できます。たとえば、地下鉄の斜面エレベーターを取ると、誰もがエレベーターに順番に入りますが、一部の人々は左から動き回るので、エレベーターを出るときの順序は異なります。人が大きな荷物をブロックした(障壁)を持っている場合、後ろの人々は周りを回ることができません:)。さらに、ここでの障壁とGCで使用される書き込み障壁は異なる概念です。
記憶障壁の分類
ほぼすべてのプロセッサは、通常フェンス(フェンス、フェンス)と呼ばれる特定の粗い穀物の障壁命令をサポートしています。通常、それはその目的に応じて、次の4つのタイプの障壁に分割されます。
ロードロードバリア
load1;ロードロード; load2;
Load1データがLoad2の前と負荷後にロードされていることを確認してください
Storestoreの障壁
store1; Storestore; store2
Store1のデータがStore2の前と後に他のプロセッサに表示されることを確認してください。
ロードストアの障壁
load1;ロードストア; store2
load1のデータがstore2の前とデータフラッシュ後にロードされていることを確認してください
Storeloadの障壁
store1; Storeload; Load2
Load2および荷重後にデータをロードする前に、Store1のデータが他のプロセッサ(メモリにフラッシュするなど)の前で表示できることを確認してください。 StoreLoad Barrierは、最近他のプロセッサによって書かれたデータではなく、ロードが古いデータを読み取るのを防ぎます。
現代のほぼすべてのマルチプロセッサには、storeLoadが必要です。 StoreLoadのオーバーヘッドは通常最大であり、StoreLoadは他の3つの障壁の効果があるため、StoreLoadは一般的な(ただしオーバーヘッド)障壁として使用できます。
したがって、上記のメモリバリアを使用して、上記の表の並べ替えルールを実装できます
| 障壁が必要です | 2番目の操作 | 2番目の操作 | 2番目の操作 | 2番目の操作 |
|---|---|---|---|---|
| 最初の操作 | 通常の読書 | 通常の書き込み | 揮発性読み取り/モニターENTER | 揮発性書き込み/モニターの出口 |
| 通常の読書 | ロードストア | |||
| 通常の読書 | Storestore | |||
| voaltile読み取り/モニターENTER | ロードロード | ロードストア | ロードロード | ロードストア |
| 揮発性書き込み/モニターの出口 | Storeload | Storestore |
最終フィールドのルールをサポートするには、最終書き込みに障壁を追加する必要があります。
X.FinalField = V; Storestore; sharedref = x;
メモリバリアを挿入します
上記のルールに基づいて、揮発性フィールドと同期キーワードの処理に障壁を追加して、メモリモデルのルールを満たすことができます。
すべての最終フィールドが書かれた後、揮発性ストアの壁の前に屋台を挿入しますが、コンストラクターが戻る前に店舗を挿入します
揮発性ストアの後にストアロードバリアを挿入します。揮発性荷重後にロードロードとロードストアのバリアを挿入します。
モニターの入力と揮発性負荷ルールは一貫しており、モニターの出口と揮発性ストアルールは一貫しています。
前に起こる
上記のさまざまな記憶障壁は、開発者にとって依然として比較的複雑であるため、JMMは説明する前に、部分的な順序関係の一連のルールを使用できます。操作Bを実行するスレッドが操作Aの結果を確認するために(AとBが同じスレッドで実行されているかどうかに関係なく)、AとBの間に関係を満たす必要があります。
RULE LURISの前に偶然
happendbeyfore fort beforeルールには含まれます
プログラムシーケンスルール:プログラムの操作Aが操作Bの前にある場合、同じスレッドの操作Aは、操作Bの前にモニターロックルールを実行します。同じモニターロックでロック操作の前にモニターロックのロック操作を実行する必要があります。
揮発性変数ルール:揮発性変数の書き込み操作は、変数の読み取り操作の前にスレッドスタートアップルールを実行する必要があります。スレッドの呼び出し。スレッドでスレッドエンドルールを実行する必要があります。スレッドでの操作はスレッドエンドルールを実行する必要があります。操作Cの前に実行され、操作Aが操作Cの前に実行されます。
ディスプレイロックには、モニターロックと同じメモリセマンティクスがあり、原子変数は揮発性と同じメモリセマンティクスを持っています。ロックの獲得と解放、揮発性変数の読み取り操作は全次の関係を満たすため、その後の揮発性読み取りの前に揮発性の書き込みを実行できます。
上記のことは、複数のルールを使用して組み合わせることができます。
たとえば、スレッドAがモニターロックに入ると、モニターロックをリリースする前の操作はプログラムシーケンスルールに基づいており、モニターリリース操作が行われます。
要約します
上記は、この記事のJavaメモリモデルJMMのすべての詳細な説明です。誰にとっても役立つことを願っています。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!