どこかで読んだことがある。
Javaのvolatileキーワードはアトミックを意味するものではありません。
volatileを宣言すると、++
の操作がアトミックになると。
演算をアトミックにするためには、synchronized
を用いて排他的なアクセスを確保する必要があります。
操作をアトミックにするためには、Javaの synchronized
メソッドやブロックを用いて排他的なアクセスを確保する必要があります。
では、2つのスレッドが同時にvolatile
プリミティブ変数を攻撃したらどうなるでしょうか。
ロックした人が先に値を設定するということですか?そして、その間に他のスレッドが現れて、最初のスレッドが値を変更している間に古い値を読んだ場合、新しいスレッドはその古い値を読まないのでしょうか?
Atomicキーワードとvolatileキーワードの違いは何ですか?
volatile`キーワードの効果は、その変数に対する個々の読み取りまたは書き込み操作がおよそアトミックであるということです。
しかし、注目すべき点は、複数回の読み取り/書き込みを必要とする操作、例えば、i++
は、i = i + 1
と同等で、1回の読み取りと1回の書き込みを行いますが、読み取りと書き込みの間に別のスレッドがi
に書き込む可能性があるため、_not_atomicです。
AtomicIntegerや
AtomicReferenceのような
Atomicクラスは、特に
AtomicInteger` のインクリメントを含め、より多様な操作をアトミックに提供します。
マルチスレッド環境には2つの重要なコンセプトがあります。
1.アトミック性 2.可視性
Volatileは可視性の問題を解決しますが,原子性の問題は解決しません.Volatile
は,コンパイラが揮発性変数の書き込みとそれに続く読み込みを含む命令を並べ替えることを防ぎます.
ここで、k++
は1つの機械語命令ではなく、3つの機械語命令です。
つまり、変数を volatile
と宣言しても、この操作をアトミックにすることはできず、他のスレッドが中間結果を見ることができ、その結果、他のスレッドにとっては古くて不要な値になってしまうのです。
しかし、AtomicInteger
やAtomicReference
は、Compare and Swap Instructionに基づいています。CASには3つのオペランドがあります: 操作対象のメモリ位置 V
, 期待される古い値 A
, 新しい値 B
です。CAS は V
を新しい値 B
にアトミックに更新します。ただし、V
の値が期待される古い値 A
と一致する場合のみで、そうでない場合は何もしません。どちらの場合でも、V
にある値を返します。これはJVMの AtomicInteger
や AtomicReference
で使用されており、それらはこの関数を compareAndSet()
として呼び出します。この機能が基礎となるプロセッサでサポートされていない場合、JVMはスピンロックによってそれを実装します。
試行錯誤しているうちに、「揮発性」は可視性のみを扱うようになってしまいました。
このスニペットをコンカレントな環境で考えてみましょう。
boolean isStopped = false;
:
:
while (!isStopped) {
// do some kind of work
}
ここでのアイデアは、あるスレッドが isStopped
の値を false から true に変更することで、後続のループにループを停止する時期であることを示すことです。
直感的には何の問題もありません。 論理的には、他のスレッドが isStopped
を true にすれば、そのループは終了しなければなりません。 実際には、他のスレッドが isStopped
を true にしたとしても、ループが終了することはないでしょう。
この理由は直感的ではありませんが、最近のプロセッサは複数のコアを持ち、各コアは他のプロセッサからはアクセスできない複数のレジスタと複数のレベルのキャッシュメモリを持っていることを考えてみてください。 つまり、あるプロセッサのローカルメモリにキャッシュされている値は、他のプロセッサで実行しているスレッドからは見えないのです。 ここに、コンカレンシーの中心的な問題のひとつである「可視性」があります。
Java Memory Modelでは、あるスレッドで行われた変数の変更がいつ他のスレッドから見えるようになるかについては何の保証もしていません。 更新が行われるとすぐに見えるようになることを保証するためには、同期をとる必要があります。
volatileキーワードは、弱い形式の同期化です。 相互排除やアトミック性には何の効果もありませんが、あるスレッドで行われた変数の変更が、その変更が行われると同時に他のスレッドから見えるようになることを保証します。 Javaでは8バイト以外の変数に対する個々の読み書きはアトミックであるため、変数を
volatile`と宣言することで、他にアトミック性や相互排除の要件がない状況で、可視性を確保するための簡単なメカニズムを提供します。