「volatileキーワードは原子性を保証できない」という理解について
2415 ワード
Volatileキーワードの浅い理解
同時プログラミングを学ぶとき、volatileキーワードには2つの役割があることがわかりました.
1.同時環境の可視性:volatile修飾後の変数はこの変数のオンラインスレッド間の可視性を保証することができ、スレッドがデータの読み書き操作を行う時、作業メモリ(CPUキャッシュ)を迂回して直接メインメモリとデータのインタラクションを行い、つまりスレッドが読み書き操作を行う時に直接メインメモリから読み取り、書き込み操作時に直接修正バックエンド変数をメインメモリにリフレッシュすることで、他のスレッドがアクセスしたデータが最新データであることを保証することができる
2.同時環境秩序性:volatile変数に対してメモリバリア(Memory barrier)方式を採用することによってコンパイル再ソートとCPU指令再ソートを防止し、具体的な方式はvolatile変数の指令を操作する前後にメモリバリアを加えることによってhappens-before関係を実現し、マルチスレッド環境でのデータ相互作用が乱れないことを保証する
Volatileは原子性を保証できない
volatileキーワードの可視性のため、その役割が誤解されやすく、以下の説明は正確ではありません.「volatile変数はすべてのスレッドに対して直ちに表示され、volatile変数に対するすべての書き込み操作はすぐに他のスレッドにフィードバックすることができ、volatile変数は各スレッドで一致するので、volatile変数に基づく演算は同時で安全です」
volatileキーワードを使用すると、スレッドが共有する変数は同時の場合に完全に表示され、スレッド情報のインタラクションと通信の役割を果たすが、非原子操作では、volatileはその操作の原子性を保証することはできない(すなわち、操作過程が他のスレッドに干渉されて情報エラーと情報損失を招く)、最も簡単な例はi++のような自己増加操作であり、以下は同時の場合の自己増加例である.
private static volatile int count = 0;
public static void main(String[] args){
Thread[] threads = new Thread[5];
for(int i = 0; i<5; i++){
threads[i] = new Thread(()->{
try{
for(int j = 0; j<10; j++){
System.out.println(++count);
Thread.sleep(500);
}
}catch (Exception e){
e.printStackTrace();
}
});
threads[i].start();
}
}
5つのスレッドが生成され、各スレッドはcountに対して10回の自己増加操作を実行し、本来は最後の結果は1~50で印刷されるべきであるが、何回実行しても所望の結果が得られず、volatileタグの変数が同時環境下でスレッドの安全を保証できないことを示している.
「i++」または「++i」のような動作は原子的動作ではない.なぜなら、自己増加動作は、3つの基本命令を含む:データの読み取り、データの計算、出力結果、i++に関連するバイトコードを見ることができる.
Code:
0: getstatic #2 // Field count:I
3: iconst_1
4: iadd
5: putstatic #2 // Field count:I
8: return
getstaticコマンドは変数をメインメモリから読み出します.この場合、変数がvolatileで修飾されている場合は、最新の情報が取得されることを完全に保証できますが、iconst_1命令(スタックに入る)とiadd命令(自己増加計算)の実行中、この変数が他のスレッドによって変更されている可能性があり、最後に計算された結果にも問題があるため、volatileは非原子操作の原子性を保証することはできず、単回の読み取りや単回の書き込みのような原子操作でのみ、volatileはスレッドの安全を実現することができる