アトムとは「それ以上分割できない最小の粒子」、アトミック・オペレーションとは「中断できない操作または一連の操作」という意味です。
アトミック演算のプロセッサ実装
プロセッサは、複雑なメモリ操作の原子性を保証するために、バス・ロックとキャッシュ・ロックの両方のメカニズムを提供します。
原子性を確保するためのバスの使用
複数のプロセッサが同時に共有変数の読み取り、書き換え、または書き込みを行うと、共有は同時に複数のプロセッサによって操作されるため、読み取り、書き換え、または書き込み操作はアトミックではなく、共有変数の値は操作後に期待される値と矛盾します。
例:i + +は、i = 1の場合、2つのi + +の操作は、期待される結果は3ですが、結果は2かもしれません。理由は、それぞれのキャッシュから同時に複数のプロセッサがiを、それぞれ、プラス1の操作を読み取り、その後、それぞれシステムメモリに書き込まれる可能性があり、その後、アトミックのその読み取り書き換え共有操作を確保したい、CPU1が共有変数を書き換えることを確認する必要がありますCPU2ができない場合CPU1は、共有変数の読み取りまたは書き込み時にCPU2は、共有変数のキャッシュメモリアドレスを操作することはできません。
原子性を確保するためのキャッシュ・ロックの使用
同時に、メモリアドレスに対する操作はアトミックであることを保証すればよいのですが、バスロックはCPUとメモリ間の通信をロックするため、ロック期間中に他のプロセッサが他のメモリアドレスのデータを操作することができなくなり、バスロックの方がオーバーヘッドになるため、現在プロセッサはバスロックの代わりにキャッシュロックを使用して最適化されている場面もあります。
キャッシュ・ロックとは、あるメモリ領域がプロセッサのキャッシュ・ラインにキャッシュされ、ロック・オペレーション中にロックされた場合、プロセッサがメモリに書き戻すロック・オペレーションを実行すると、プロセッサはバス上でLOCK#信号をアサートせず、代わりに内部メモリ・アドレスを変更し、キャッシュ・コヒーレンス・メカニズムがオペレーションの原子性を保証できるようにすることを意味します。なぜなら、キャッシュ・コヒーレンシ・メカニズムは、2つ以上のプロセッサがキャッシュしているメモリ領域のデータを同時に変更することを防ぎ、他のプロセッサがロックされているキャッシュ・ラインのデータを書き戻したときにキャッシュ・ラインを無効にするからです。図2-3に示す例では、CPU1がキャッシュ・ラインのiを変更するときにキャッシュ・ロックを使用すると、CPU2は同時にiのキャッシュ・ラインをキャッシュすることができません。
しかし、プロセッサがキャッシュ・ロックを使用しない状況が2つあります:
- 1つ目は、プロセッサ内部で操作のデータをキャッシュできない場合、または操作のデータが複数のキャッシュラインにまたがる場合です。
- 2つ目は、キャッシュ・ロックをサポートしていないプロセッサがある場合です。インテル486とPentiumプロセッサの場合、ロックされたメモリ領域がプロセッサのキャッシュ・ラインにあっても、バス・ロックが呼び出されます。
アトミック演算のJAVA実装
アトミック演算は、ロックとサイクリックCASによってJAVAに実装することができます。
巡回CASによるアトミック操作
CASはCompareAndSwap、比較と置換です。
VMのCAS演算は、プロセッサが提供するCMPXCHG(Compare and Exchange)命令を使用して実装されます。スピンCASの実装の基本的な考え方は、CAS演算が成功するまでループすることです。
Java 1.5以降、JDKの同時実行パッケージには、AtomicBoolean、AtomicInteger、AtomicLongなどのアトミック演算を実装するクラスが用意されています。これらのアトミック・ラッパークラスは、アトミックな方法で現在値を1だけ自己インクリメントしたり、1だけデクリメントしたりする便利なユーティリティメソッドも提供します。
アトミック演算のCAS実装に関する3つの主な問題
Javaの並行処理パッケージには、スピンCASを使用してアトミック操作を実装する並行処理フレームワークもあります。 CASはアトミック操作に対して非常に効率的なソリューションですが、CASにはABA問題、長いループ時間と高いオーバーヘッド、アトミック操作が1つの共有変数に対してのみ保証されるという3つの大きな問題があります。
ABA
ABA問題は、バージョン番号を変数に付加することで解決します。バージョン番号を付加する変数の前で、変数がバージョン番号プラス1に更新されるたびに、A→B→Aは1A→2B→3Aになります。Java 1.5以降、JDK AtomicパッケージはAtomicStampedReferenceクラスを提供し、ABA問題を解決します。このクラスのcompareAndSetメソッドの機能は、まず現在の参照が期待される参照と等しいかどうかをチェックし、現在のフラグが期待されるフラグと等しいかどうかをチェックします。
長いループ時間と高いオーバーヘッド
スピンCASが長時間失敗すると、CPUに非常に大きな実行オーバーヘッドを課すことになります。JVMがプロセッサが提供する一時停止命令をサポートすることができれば、効率はある程度改善されます。一時停止命令には2つの効果があります:第一に、CPUがあまりにも多くの実行リソースを消費しないように、命令のパイプライン実行を遅延させることができ、遅延時間は遅延時間のバージョンの特定の実装に依存し、いくつかのプロセッサではゼロです。第二に、メモリ順序の衝突によるループの終了時にCPUのパイプラインが空になるのを避けることができ、CPUの実行効率が向上します。
共有変数に対するアトミック操作は1回のみ保証
共有変数に対して操作を行う場合、サイクリックCASを使用すればアトミックな操作が保証されますが、複数の共有変数に対して操作を行う場合、サイクリックCASではアトミック性が保証されないため、この時はロックを使用します。もう一つ厄介なのは、複数の共有変数を一つの共有変数にまとめて操作する方法です。例えば、2つの共有変数i = 2, j = aがあり、ij = 2aを結合し、CASを使ってijを操作します。Java 1.5以降では、参照オブジェクト間のアトミック性を保証するAtomicReferenceクラスがJDKに用意されているので、1つのオブジェクトに複数の変数を入れてCAS操作を行うことができます。
ロック機構を利用したアトミック操作
ロック機構は、ロックを取得したスレッドだけがロックされたメモリ領域を操作できるようにします。JVMは、バイアスロック、軽量ロック、相互排他ロックなど、多くの種類のロック機構を内部的に実装しています。バイアス・ロックに加えて、JVMはサイクリックCASを使用したロックを実装しています。つまり、スレッドが同期ブロックに入りたいときは、サイクリックCASを使用してロックを取得し、同期ブロックから出るときは、サイクリックCASを使用してロックを解放します。
Study Source Javaにおける並行プログラミングの技術
個人的な意見です。議論へようこそ、個人ブログ