Java の基本チュートリアルカラムの紹介と分析 Java CAS
1. はじめに
CAS は、compare and swap の略です。マルチスレッド環境で同期機能を実装するためのものです。 CAS 操作には、メモリ位置、期待値、新しい値という 3 つのオペランドが含まれます。 CAS の実装ロジックでは、メモリ位置の値と期待値を比較し、それらが等しい場合、メモリ位置の値を新しい値に置き換えます。等しくない場合、操作は実行されません。 Java では、Java は CAS を直接実装せず、CAS 関連の実装は C インライン アセンブリの形式で実装されます。 Java コードは JNI 経由で呼び出す必要があります。第 3 章で実装の詳細を分析します。 前述したように、CAS の操作プロセスは難しくありません。しかし、上記の説明だけでは十分ではないので、次にその他の予備知識をいくつか紹介します。この背景知識があるだけで、その後の内容をよりよく理解できます。2. 背景の紹介
CPU がバスとメモリを介してデータを送信することは誰もが知っています。マルチコア時代では、複数のコアが同じバスを介してメモリや他のハードウェアと通信します。以下に示すように: 画像出典: 「コンピュータ システムの徹底理解」 上の図は、比較的単純なコンピュータの構造図です。 、質問を説明するのに十分です。上の図では、CPU は 2 つの青い矢印でマークされたバスを介してメモリと通信します。 CPU の複数のコアが同じメモリ上で同時に動作する場合、それを制御しないとどのようなエラーが発生しますか?ここで簡単に説明すると、コア 1 が 32 ビット帯域幅のバスを介して 64 ビット データをメモリに書き込むと仮定すると、コア 1 は操作全体を完了するために 2 回書き込む必要があります。コア 1 が初めて 32 ビット データを書き込んだ後、コア 2 はコア 1 によって書き込まれたメモリ位置から 64 ビット データを読み取ります。コア 1 はすべての 64 ビット データをメモリに完全に書き込んでいないため、コア 2 はこのメモリ位置からデータの読み取りを開始するため、読み取られたデータはカオスになるはずです。 しかし、実際には、この問題について心配する必要はありません。 Intel Developer Manual を読むと、Pentium プロセッサ以降、Intel プロセッサは 64 ビット境界にアライメントされたクワッドワードのアトミックな読み取りと書き込みを保証するようになることがわかります。 上記の説明に基づいて、Intel プロセッサはシングルアクセスのメモリアライメント命令がアトミックに実行されることを保証できると結論付けることができます。しかし、それがメモリに 2 回アクセスする命令だった場合はどうなるでしょうか?答えは保証されません。たとえば、インクリメント命令inc dword ptr [...] は
DEST = DEST 1 と同等です。この命令には、2 つのメモリ アクセスを伴う 3 つの操作
Read->Modify->Writeが含まれています。値 1 がメモリ内の指定された場所に格納されている状況を考えてみましょう。これで、両方の CPU コアが同時に命令を実行します。 2 つのコアの交互実行のプロセスは次のとおりです。
付随する命令の実行中にプロセッサの LOCK# 信号をアサートします (説明されている重要なポイントマルチプロセッサ環境では、LOCK# 信号によりプロセッサが一部の共有メモリを排他的に使用できることが太字で強調表示されています。ロックは、次の命令の前に追加できます: ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、CMPXCHG16B、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR、XADD 、XCHG.
命令を次のように変換します)。アトミック命令 ) マルチプロセッサ環境では、LOCK# 信号 は、信号がアサートされている間、プロセッサが共有メモリを排他的に使用できるようにします 。
inc 命令の前にロック プレフィックスを追加すると、命令をアトミックにすることができます。複数のコアが同じ inc 命令を同時に実行する場合、それらはシリアル方式で実行されるため、上記の状況は回避されます。ここで別の質問がありますが、ロック プレフィックスはどのようにしてコアが特定のメモリ領域を排他的に占有することを保証するのでしょうか?答えは次のとおりです。
Intel プロセッサでは、プロセッサの特定のコアが特定のメモリ領域を排他的に占有するようにする方法が 2 つあります。 1 つ目の方法は、バスをロックして特定のコアにバスを独占的に使用させることですが、これはコストがかかりすぎます。バスがロックされると、他のコアはメモリにアクセスできなくなり、他のコアが短時間動作を停止する可能性があります。 2 番目の方法は、一部のメモリ データがプロセッサ キャッシュにキャッシュされている場合に、キャッシュをロックすることです。プロセッサが発行する LOCK# 信号はバスをロックするのではなく、キャッシュ ラインに対応するメモリ領域をロックします。このメモリ領域がロックされている間、他のプロセッサはこのメモリ領域に対して関連する操作を実行できません。バスをロックする場合と比較して、キャッシュをロックするコストは明らかに小さくなります。バス ロックとキャッシュ ロックの詳細については、Intel Developer's Manual Volume 3 Software Developer's Manual、第 8 章マルチプロセッサ管理を参照してください。
3. ソース コードの分析
上記の背景知識を踏まえて、CAS のソース コードをゆっくり読むことができます。この章の内容では、java.util.concurrent.atomic パッケージにあるアトミック クラス AtomicInteger の CompareAndSet メソッドを分析します。関連する分析は次のとおりです:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
上記の分析はさらに多いようですが、主な処理は複雑ではありません。コードの詳細にこだわらなければ、比較的簡単に理解できます。次に、Windows プラットフォームでの Atomic::cmpxchg 関数を分析します。読む。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
上記のコードは、LOCK_IF_MP プリコンパイル済み識別子と cmpxchg 関数で構成されています。もう少しわかりやすくするために、cmpxchg 関数の LOCK_IF_MP を実際の内容に置き換えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
CAS の実装プロセスはここで終了です CAS の実装はプロセッサのサポートと切り離せません。上記には非常に多くのコードがありますが、コア コードは実際にはロック プレフィックスが付いた cmpxchg 命令、つまり lock cmpxchg dword ptr [edx], ecx
です。
4. ABA 問題
CAS について話すときは、基本的に CAS の ABA 問題について話さなければなりません。 CAS は、「読み取り -> 比較 -> ライトバック」という 3 つのステップで構成されます。スレッド 1 とスレッド 2 が CAS ロジックを同時に実行する状況を考えます。2 つのスレッドの実行シーケンスは次のとおりです:
上記のプロセスと同様、スレッド 1 は元の値が変更されたことを認識せず、変更されているように見えます。変化がないため、プロセスは引き続き実行されます。 ABA の問題の場合、通常の解決策は、CAS 操作ごとにバージョン番号を設定することです。 java.util.concurrent.atomic パッケージは、ABA の問題を処理できるアトミック クラス AtomicStampedReference を提供します。特定の実装はここでは分析されません。興味のある友人は自分で確認してください。
5. まとめ
これを書きながら、この記事もいよいよ終わりに近づいてきました。 CAS の原理自体は実装も含めて難しくありませんが、実際に書くのは簡単ではありません。これには低レベルの知識が必要で、理解はできますが、理解するのはまだ少し難しいです。私には基礎知識が不足しているため、上記の分析の一部は必然的に間違っている可能性があります。間違いがある場合は、お気軽にコメントしてください。もちろん、なぜ間違っているのかを説明するのが最善です。ありがとうございます。
さて、この記事はここまでです。読んでくれてありがとう、それではさようなら。
付録
前のソース コード分析セクションで使用したいくつかのファイルへのパスがここに掲載されています。次のように、誰もがインデックスを付けるのに役立ちます:
#atomic_windows_x86.inline.hppopenjdk/hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hppファイル名 | パス |
---|---|
Unsafe.java | openjdk/jdk/src/share/classes/sun/misc/Unsafe.java |
unsafe.cpp | openjdk/ hotspot/src/share/vm/prims/unsafe.cpp |
atomic.cpp | openjdk/hotspot/src/share/vm/runtime/atomic.cpp |
以上がJava CAS 原則分析の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。