はじめに|“見えない変更”があなたのバグの原因かも?
Javaでマルチスレッドのコードを書いていると、「なぜか止まらない」「処理が動いたり動かなかったりする」といった不思議なバグに遭遇することがあります。
その原因の多くは、**「スレッド間で共有している変数の変更が見えていない」**という、可視性の問題です。
そんなときに登場するのが、volatile というキーワード。
簡単そうに見えるこのキーワードですが、使いどころを間違えると逆に危険です。
本記事では、Java初心者〜中級者向けに、
- 可視性とは何か?
volatileの正体とできること- 使っていい場面/ダメな場面
- 実務での活用法
をやさしい言葉と具体例で解説します。
1. そもそも可視性とは?Javaの見えないバグの正体
✔ 可視性とは「変数の変更が他のスレッドに見えること」
Javaでは、複数のスレッドで同じ変数を使っているとき、あるスレッドで値を変更しても、別のスレッドがその変更を見られないことがあるのです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Shared { boolean running = true; void stop() { running = false; } void run() { while (running) { // ループ処理 } } } |
このコード、stop()でrunningをfalseにしたら、run()のループが止まるはずですよね?
でも実際は、止まりません。
その理由は、runningの変更が「run()側から見えていない」から。
CPUやJavaの最適化によって、runningの変更がキャッシュにとどまっていたり、順番が入れ替わったりするからです。
2. Javaメモリモデルと可視性のギャップ
Javaでは、各スレッドがローカルキャッシュのような仕組みを持っており、変数の値をメインメモリと同期しないことがあります。
このときに、別のスレッドが古い値を見てしまう現象が起きます。
これが「可視性の問題」です。
3. そこで登場、volatileキーワード!
✔ 役割1:可視性を保証する
volatileをつけると、「この変数は変更されたら、すぐに他のスレッドに見えるようにする」という命令になります。
|
1 2 |
volatile boolean running = true; |
これだけで、stop()からの変更が、すぐにrun()にも反映されるようになります。
✔ 役割2:書き込み・読み込みの順序を守る(happens-before)
Javaにはhappens-beforeというルールがあり、volatileを使うと:
- 書き込み → 読み込み の順番が守られる
- 書き込んだ側の他の処理も、読み込む側から見えるようになる
という、安全なスレッド間の通信が可能になります。
4. でも注意!volatileは万能ではない
❌ 原子性(atomicity)は保証しない
volatile int count;のように使っても、以下の操作は安全ではありません。
|
1 2 |
count++; // 危険! |
これは実は:
- 値を読み込む
- 1を足す
- 結果を書き戻す
という3つのステップで実行されるため、複数スレッドが同時にやると上書きされます。
5. volatileを使って良い場面・ダメな場面
✅ 適切な場面
- フラグの制御(停止、開始など)
- 参照の切り替え(オブジェクトの差し替え)
- 読み込みが多く、書き込みが少ない
❌ 適さない場面
- 複雑な操作(インクリメント、条件付き更新など)
- 複数の変数をまとめて扱うとき
- 複雑なスレッド間の同期が必要なとき
6. volatileの具体例を見てみよう!
✔ OKな例:停止フラグ
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Worker { private volatile boolean stop = false; void stopWork() { stop = true; } void doWork() { while (!stop) { // 処理中 } } } |
このような「状態を切り替えるだけ」の用途なら、volatileがぴったりです。
✔ NGな例:カウンタ
|
1 2 3 4 5 6 7 8 |
class Counter { private volatile int count = 0; void increment() { count++; // ダメ!競合します } } |
→ こういうときは AtomicInteger や synchronized を使いましょう。
7. AtomicIntegerとの違いは?
| 特徴 | volatile | AtomicInteger |
|---|---|---|
| 可視性 | ◎ | ◎ |
| 原子性 | ✕ | ◎ |
| 複雑な操作 | ✕ | △(ある程度可能) |
| パフォーマンス | ◎(軽量) | ○ |
8. 実務での設計ポイント
✔ 設計上のチェックリスト
- 共有する変数は、読み書きの頻度・意味合いを整理
- 状態切り替えのみなら
volatile、それ以外はsynchronizedやAtomicを検討 volatileに頼りすぎず、設計で解決できないかを考える- IDEや静的解析ツールで並行処理の問題を事前に検出
9. volatileを使うときのアンチパターン
❌ 「とりあえず volatile 付けとく」
→ 意味のない場面でつけても、可視性だけが保証されて競合は解決しない。
❌ volatileだけで安全だと思い込む
→ 競合の多い処理はvolatileでは防げません。
10. まとめ:volatileは“軽い同期”の道具
Javaの並行処理は難しいですが、volatileはその中で**「軽量で使いやすい」同期の選択肢**です。
- 状態フラグや参照の切り替えに最適
- でも原子性は保証しないので、使いどころを見極めよう
- 正しく使えば、バグを未然に防ぐ最強の武器になります!
さらに深く学びたい人へ
volatileやAtomicInteger、synchronizedなどをマスターすれば、Javaでのマルチスレッド設計に自信がつきます。
本気でJavaを学びたい方には:
👉 絶対にJavaプログラマーになりたい人へ。
で並行処理の基礎から学び直し、
👉 サイゼントアカデミー
でコードレビューや実務設計力を鍛えるのがオススメです!

コメント