Javaのvolatileとhappens-before:可視性・順序性・原子性の境界
基準となる環境はJava 17+です。本文ではJava Memory Modelを、特定のCPUアーキテクチャに依存せずに説明します。
1. volatileが保証すること
volatile変数への書き込みは、その後別のスレッドが同じ変数を読み取る操作との間にhappens-before関係を確立します。可視性と一定程度の順序性を保証しますが、一連の操作をアトミックな操作に変えるものではありません。
volatile boolean stoppedは、停止フラグの公開に使えます。volatile int countでcount++を安全に実装することはできません。自増には、読み取り、1を加える、書き戻しの3つの操作が含まれるためです。
2. 適切な手段を選ぶ
単一変数の原子的な更新にはAtomicIntegerやAtomicReferenceを使います。複合的な状態遷移には、ロック、並行コンテナ、CASループなどを使います。不変の構成を公開する用途ではvolatile参照を使えます。低レベルのメモリ順序が必要な場合に限り、VarHandleを検討します。
3. happens-beforeの成立要因
スレッドの開始と終了、ロックの解放と取得、Futureの完了、並行コンテナで規定されている操作でも、可視性関係を確立できます。「命令の並べ替えを禁止する」という表現で、JMMのルールを代用しないでください。
4. 検証
複雑なメモリ順序にはjcstressを、パフォーマンス比較にはJMHを使います。競合と障害注入を含む並行テストで、業務状態の書き込みを検証します。volatileはロックではなく、複合的な操作に対する原子性の保証でもありません。