行の下のどこかを読みます。
Java volatileキーワードはアトミックを意味しません。volatileを宣言した後、
++
操作はアトミックになるという一般的な誤解は、Javaでsynchronized
メソッドまたはブロックを使用して排他アクセスを保証する必要がある操作をアトミックにするために。
では、2つのスレッドがvolatile
プリミティブ変数を同時に攻撃するとどうなりますか?
これは、だれでもロックを取得し、その値を最初に設定することを意味します。その間に、最初のスレッドが値を変更している間に他のスレッドが起動して古い値を読み取った場合、新しいスレッドは古い値を読み取らないでしょうか?
Atomicキーワードとvolatileキーワードの違いは何ですか?
volatile
キーワードの効果は、おおよそ、その変数に対する個々の読み取りまたは書き込み操作がアトミックであることです。
ただし、特に、1回の読み取りと1回の書き込みを行うi++
と同等のi = i + 1
など、複数の読み取り/書き込みを必要とする操作はnotアトミック。読み取りと書き込みの間に別のスレッドがi
に書き込むことがあるためです。
Atomic
やAtomicInteger
などのAtomicReference
クラスは、特にAtomicInteger
の増分を含む、さまざまな操作をアトミックに提供します。
揮発性とアトミックは2つの異なる概念です。揮発性により、特定の予想される(メモリ)状態が異なるスレッド間で真であることが保証され、Atomicsでは変数に対する操作がアトミックに実行されます。
Javaの2つのスレッドの例を次に示します。
スレッドA:
value = 1;
done = true;
スレッドB:
if (done)
System.out.println(value);
value = 0
およびdone = false
で始まるスレッドのルールは、スレッドBが値を出力するかどうかは未定義であることを示しています。 さらにvalueもその時点では未定義です!これを説明するには、Javaについて少し知る必要があります要するに、メモリ管理(複雑になる可能性があります):スレッドは変数のローカルコピーを作成する場合があり、JVMはコードを並べ替えて最適化できるため、上記のコードが正確にその順序で実行される保証はありません。 doneをtrueに設定し、thenに値を1に設定すると、JIT最適化の結果となる可能性があります。
volatile
は、そのような変数へのアクセス時に、新しい値が他のすべてのスレッドからすぐに見えるようにすることだけを保証しますおよび実行順序は、コードがあなたがそれを期待するであろう状態。したがって、上記のコードの場合、done
をvolatileとして定義すると、スレッドBが変数をチェックするたびにfalseになるか、 true、およびtrueの場合、value
も1に設定されています。
volatileの副作用として、そのような変数の値はスレッド単位でアトミックに設定されます(実行速度はごくわずかです)。ただし、これは32ビットシステムでのみ重要です。長い(64ビット)変数(または同様の)を使用します。他のほとんどの場合、変数の設定/読み取りはとにかくアトミックです。ただし、アトミックアクセスとアトミック操作には重要な違いがあります。揮発性は、アクセスがアトミックであることのみを保証し、アトミックは、operationがアトミックであることを保証します。
次の例をご覧ください。
i = i + 1;
Iをどのように定義しても、上記の行が実行されたときに値を読み取る別のスレッドがiまたはi + 1を取得する可能性があるのは、operationアトミックではありません。他のスレッドがiを異なる値に設定すると、最悪の場合、古い値に基づいてi + 1を計算している最中であるため、スレッドAによって以前の値に戻すことができます。再びその古い値+ 1になります。説明:
Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1
AtomicIntegerのようなアトミックは、そのような操作がアトミックに発生するようにします。したがって、上記の問題は発生しません。両方のスレッドが終了すると、1000または1001になります。
マルチスレッド環境には2つの重要な概念があります。
Volatile
は可視性の問題を根絶しますが、原子性を処理しません。 Volatile
は、コンパイラが揮発性変数の書き込みとその後の読み取りを含む命令を並べ替えることを防ぎます。例えばk++
ここでk++
は単一の機械語命令ではなく、3つの機械語命令です。
したがって、変数をvolatile
に宣言しても、この操作はアトミックになりません。つまり、別のスレッドは、他のスレッドの古い値または不要な値である中間結果を見ることができます。
ただし、AtomicInteger
、AtomicReference
は、 比較およびスワップ命令 に基づいています。 CASには3つのオペランドがあります。メモリロケーションV
操作対象、予想される古い値A
、および新しい値B
です。 CAS
はアトミックにV
を新しい値B
に更新しますが、V
の値が予想される古い値と一致する場合のみA
;それ以外の場合は何もしません。どちらの場合でも、現在V
にある値を返します。これはAtomicInteger
、AtomicReference
でJVMによって使用され、compareAndSet()
として関数を呼び出します。この機能が基になるプロセッサでサポートされていない場合、JVMは spin lock で実装します。
示されているように、volatile
は可視性のみを扱います。
並行環境でこのスニペットを検討してください。
boolean isStopped = false;
:
:
while (!isStopped) {
// do some kind of work
}
ここでのアイデアは、一部のスレッドがisStopped
の値をfalseからtrueに変更して、ループを停止する時間であることを後続のループに示すことができるということです。
直感的には、問題はありません。論理的に別のスレッドがisStopped
をtrueにした場合、ループは終了する必要があります。現実には、別のスレッドがisStopped
をtrueに設定しても、ループは終了しない可能性があります。
この理由は直感的ではありませんが、最新のプロセッサには複数のコアがあり、各コアには複数のレジスタと他のプロセッサからアクセスできないの複数レベルのキャッシュメモリがあることを考慮してください。つまり、1つのプロセッサのローカルメモリにキャッシュされている値は、異なるプロセッサで実行されているスレッドに対して可視ではないです。ここに、並行性に関する中心的な問題の1つ、可視性があります。
Java Memory Modelは、スレッド上の変数に加えられた変更が他のスレッドから見えるようになる時期については一切保証しません。更新が行われるとすぐに表示されるようにするには、同期する必要があります。
volatile
キーワードは、弱い形式の同期です。相互排除や原子性については何も行いませんが、あるスレッドの変数に加えられた変更が、作成されるとすぐに他のスレッドから見えるようになるという保証はありません。 Javaでは8バイトではない変数への個々の読み取りおよび書き込みはアトミックであるため、変数volatile
を宣言すると、他のアトミック性または相互排除の要件がない状況で可視性を提供する簡単なメカニズムが提供されます。
では、2つのスレッドが同時に揮発性プリミティブ変数を攻撃するとどうなりますか?
通常、それぞれが値をインクリメントできます。ただし、両方が同時に値を更新し、合計で2ずつ増加する代わりに、両方のスレッドが1ずつ増加し、1のみが追加されます。
これは、だれでもロックを取得することを意味しますか。つまり、最初にその値を設定します。
ロックはありません。それがsynchronized
の目的です。
そしてその間に、最初のスレッドが値を変更している間に他のスレッドが起動して古い値を読み取ると、新しいスレッドは古い値を読み取らないのですか?
はい、
Atomicキーワードとvolatileキーワードの違いは何ですか?
AtomicXxxxはvolatileをラップするため、基本的に同じですが、違いは、増分を実装するために使用されるCompareAndSwapなどの高レベルの操作を提供することです。
AtomicXxxxはlazySetもサポートしています。これは揮発性セットのようなものですが、書き込みが完了するのを待っているパイプラインを失速させません。つまり、値を読んだだけで書いた場合、古い値が表示される可能性がありますが、とにかくすべきではないということです。違いは、volatileの設定には約5 nsかかり、ビットlazySetには約0.5 nsかかります。