volatile は、変数の修飾子であり、すべてのスレッドによってその変数の変更が可視化されることを保証します。変数がvolatileとして宣言されている場合、その読み取りと書き込みはローカルキャッシュをバイパスして主メモリから直接行われます。これにより、スレッド内で値のキャッシュが防止されます。
synchronized は、可視性だけでなく、相互排除(mutual exclusion)も保証するキーワードです:同時に1つのオブジェクトの同期ブロックを実行できるのは、1つのスレッドだけです。
volatile 変数は、次の条件に基づいて単純なフラグやカウンタに使用する必要があります:
volatileの使用例:
class Flag { private volatile boolean running = true; public void stop() { running = false; } public void loop() { while (running) { // ... } } }
volatileを使用して変数のインクリメントの原子性を保証できますか?
答え:
いいえ、volatile は操作の原子性を保証しません。例えば、counter++ は原子操作ではありません。たとえ counter がvolatileとして宣言されていても、操作は読み取り、変更、書き込み(複数のアクション)が含まれているため、他のスレッドによって中断される可能性があります。原子性は、synchronizedまたはjava.util.concurrent.atomicパッケージのクラスを使用して保証されます。
例:
class Counter { private volatile int count = 0; public void increment() { count++; // 原子操作ではありません! } }
物語
オンラインショップのプロジェクトで、注文処理スレッドの終了フラグがvolatileなしで実装されました。その結果、1つのスレッドが無限ループに「ハング」し、他のスレッドからの変数の変更が見えませんでした。診断には数日かかりました。
物語
金融システムで、開発者は操作のカウンタにvolatile intを使用しました。ピーク負荷時に操作の数が「失われ始めました」。理由は、複雑なインクリメント操作の原子性の喪失です。
物語
開発者は、クリティカルセクションへのアクセスを相互排除するためにvolatileを使用しようとして、volatileとsynchronizedを混同しました。これにより、データレースやマルチスレッドアプリケーションでのキャッチしにくいバグが発生しました。