背景:
Javaが登場したとき、メモリとスレッドがどのように相互作用するかについての正式な説明は存在しませんでした。その結果、同じプログラムが異なるJVM上で異なる動作をすることがあり、難解なバグを引き起こすことがありました。2004年に、Java仕様にJavaメモリモデル(JMM)が追加され、スレッドが変数の更新をどのように見るかを厳密に定義することを目的としました。
問題:
明確なメモリモデルがないと、書き込みおよび読み込み操作がコンパイラまたはプロセッサによって混合されてしまい、異なるスレッドから共有変数へのアクセス時に予期しない動作を引き起こす可能性があります。これにより、競合状態、可視性の問題、デバッグが難しい同期のエラーが発生する可能性があります。
解決策:
JMMは、あるスレッドによる変更が他のスレッドにいつ可視化されるかについてのルールを定義します。これにより、_happens-before_の概念、同期化(synchronized、volatile、final)、内部ロックが設定され、マルチスレッド環境における命令の再整理が制限されます。
コード例:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }
主な特徴:
なぜvolatileは操作を原子性にしないのか、ただ可視性を保証するだけなのか?
Volatileは、スレッド間での変更の可視性のみを保証しますが、変更の原子性は保証しません。例えば、volatile変数のインクリメントは原子操作ではなく、読み取り、変更、書き込みの行動で構成されます。
コード例:
volatile int count = 0; count++; // 原子性なし!
synchronizedブロックはその外での変更の可視性を保証できますか?
はい。synchronizedブロックを出た後、内部で行われたすべての変更は、同じオブジェクトモニターのブロックに入る他のスレッドに見えるようになります。
finalフィールドの変更はいつ他のスレッドに可視化されますか?
Finalフィールドは、オブジェクトが他のスレッドに参照を渡す前に完全に構築されている場合にのみ、正確な公開を保証します。例えば、不変のオブジェクトを通じてです。そうでなければ、未初期化の状態が可視化される可能性があります。
ネガティブケース
開発者は、複数のスレッドからのシンプルなvolatileインクリメントを使用してサイトのアクセスカウントを増加させることを決定しました。
利点:
欠点:
ポジティブケース
AtomicIntegerをカウンターやsynchronizedメソッドで使用。
利点:
欠点: