web-dev-qa-db-ja.com

揮発性対原子

行の下のどこかを読みます。

Java volatileキーワードはアトミックを意味しません。volatileを宣言した後、++操作はアトミックになるという一般的な誤解は、Javaでsynchronizedメソッドまたはブロックを使用して排他アクセスを保証する必要がある操作をアトミックにするために。

では、2つのスレッドがvolatileプリミティブ変数を同時に攻撃するとどうなりますか?

これは、だれでもロックを取得し、その値を最初に設定することを意味します。その間に、最初のスレッドが値を変更している間に他のスレッドが起動して古い値を読み取った場合、新しいスレッドは古い値を読み取らないでしょうか?

Atomicキーワードとvolatileキーワードの違いは何ですか?

101
Vaibhav

volatileキーワードの効果は、おおよそ、その変数に対する個々の読み取りまたは書き込み操作がアトミックであることです。

ただし、特に、1回の読み取りと1回の書き込みを行うi++と同等のi = i + 1など、複数の読み取り/書き込みを必要とする操作はnotアトミック。読み取りと書き込みの間に別のスレッドがiに書き込むことがあるためです。

AtomicAtomicIntegerなどのAtomicReferenceクラスは、特にAtomicIntegerの増分を含む、さまざまな操作をアトミックに提供します。

124
Louis Wasserman

揮発性とアトミックは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は、そのような変数へのアクセス時に、新しい値が他のすべてのスレッドからすぐに見えるようにすることだけを保証しますおよび実行順序は、コードがあなたがそれを期待するであろう状態。したがって、上記のコードの場合、donevolatileとして定義すると、スレッド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になります。

67
TwoThe

マルチスレッド環境には2つの重要な概念があります。

  1. 原子性
  2. 可視性

Volatileは可視性の問題を根絶しますが、原子性を処理しません。 Volatileは、コンパイラが揮発性変数の書き込みとその後の読み取りを含む命令を並べ替えることを防ぎます。例えばk++ここでk++は単一の機械語命令ではなく、3つの機械語命令です。

  1. 値をコピーして登録する
  2. それを増やす
  3. 戻す

したがって、変数をvolatileに宣言しても、この操作はアトミックになりません。つまり、別のスレッドは、他のスレッドの古い値または不要な値である中間結果を見ることができます。

ただし、AtomicIntegerAtomicReferenceは、 比較およびスワップ命令 に基づいています。 CASには3つのオペランドがあります。メモリロケーションV操作対象、予想される古い値A、および新しい値Bです。 CASはアトミックにVを新しい値Bに更新しますが、Vの値が予想される古い値と一致する場合のみA;それ以外の場合は何もしません。どちらの場合でも、現在Vにある値を返します。これはAtomicIntegerAtomicReferenceでJVMによって使用され、compareAndSet()として関数を呼び出します。この機能が基になるプロセッサでサポートされていない場合、JVMは spin lock で実装します。

45
Trying

示されているように、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を宣言すると、他のアトミック性または相互排除の要件がない状況で可視性を提供する簡単なメカニズムが提供されます。

22
scottb

では、2つのスレッドが同時に揮発性プリミティブ変数を攻撃するとどうなりますか?

通常、それぞれが値をインクリメントできます。ただし、両方が同時に値を更新し、合計で2ずつ増加する代わりに、両方のスレッドが1ずつ増加し、1のみが追加されます。

これは、だれでもロックを取得することを意味しますか。つまり、最初にその値を設定します。

ロックはありません。それがsynchronizedの目的です。

そしてその間に、最初のスレッドが値を変更している間に他のスレッドが起動して古い値を読み取ると、新しいスレッドは古い値を読み取らないのですか?

はい、

Atomicキーワードとvolatileキーワードの違いは何ですか?

AtomicXxxxはvolatileをラップするため、基本的に同じですが、違いは、増分を実装するために使用されるCompareAndSwapなどの高レベルの操作を提供することです。

AtomicXxxxはlazySetもサポートしています。これは揮発性セットのようなものですが、書き込みが完了するのを待っているパイプラインを失速させません。つまり、値を読んだだけで書いた場合、古い値が表示される可能性がありますが、とにかくすべきではないということです。違いは、volatileの設定には約5 nsかかり、ビットlazySetには約0.5 nsかかります。

12
Peter Lawrey