同期メカニズム
データ一貫性の原則
マルチスレッドアプリケーションを作成する際、データ一貫性を確保するための2つの原則:
相互排他 (Mutual Exclusion)
クリティカルセクション内のコードは、一度に1つのスレッドのみが実行できます。つまり、同時に1つのスレッドのみが変数にアクセスできます。
可視性 (Visibility)
共有データの値があるスレッドによって変更された場合、他のスレッドがすぐにその変更を確認できます。
volatile キーワード
Javaでは、各スレッドが独自のメモリ空間(ワーキングメモリ)を持っています。操作を実行した後、スレッドは使用した変数の値をメインメモリに更新し、他のスレッドはメインメモリから最新の値を読み取ることができます。
この特性はプログラムの処理効率を向上させますが、マルチスレッド環境では変数の可視性問題を引き起こす可能性があります。
変数の読み取りと書き込みが異なるスレッドで発生する場合、読み取りスレッドが変数の値の変更を適時に確認できない場合があり、データの不整合につながります。このような場合、変数の前に
volatile を付けると、その変数は各スレッドのワーキングメモリを使用せず、常にメインメモリから読み書きするようになります。synchronized キーワード
異なるスレッドが同じリソースに同時にアクセスする可能性があります。synchronized の目的は、一度に1つのスレッドのみが同じリソースを使用できるように制御することです。
プログラムで例外が発生した場合、スレッドとロックは自動的に解放され、デッドロックは発生しません。ただし、待機中のスレッドを中断させることはできず、待機中のスレッドは待ち続けます。
ロックオブジェクト (Lock Object)
ロックとして使用されるオブジェクト:
synchronized method- ロックは現在のオブジェクトインスタンスstatic synchronized method- ロックは現在のクラスの Class オブジェクトsynchronized(obj)- ロックは括弧内で指定されたオブジェクト
synchronized の制限
- 同時に1つのスレッドのみが synchronized ブロックに入ることができます
- 待機中のスレッドが synchronized ブロックを取得する順序は保証されません
パフォーマンスの考慮
- 低オーバーヘッド:synchronized ブロックが競合していない場合(まだロックされていない)
- 高オーバーヘッド:synchronized ブロックが競合している場合(別のスレッドによってすでにロックされている)
volatile vs synchronized
| 特性 | volatile | synchronized |
|---|---|---|
| 相互排他 | いいえ | はい |
| 可視性 | はい | はい |
| 適用場面 | 単純な読み書き操作 | 複合操作 |
| パフォーマンス | 良い | 悪い |
volatile は synchronized でブロックをロックするのに比べて、より簡潔なコードを書くのに役立ちます。volatile を使用すると、同期の責任を JVM に委ねるため、自分で処理するよりもエラーが発生しにくくなります。ただし、volatile と宣言された変数が頻繁に使用される場合、ブロック全体をロックするよりもパフォーマンスが低下する可能性があります。CPU キャッシュコヒーレンス
flowchart LR
subgraph CPU1[CPU Core 1]
C1[Cache]
end
subgraph CPU2[CPU Core 2]
C2[Cache]
end
MM[Main Memory]
C1 <--> MM
C2 <--> MM
可視性問題
異なる CPU コアのスレッドが同じメモリ(クラスなど)を共有する場合、適切な同期メカニズムがないと、あるスレッドによる変数の変更が他のスレッドに適時に反映されない可能性があります。
Happens-Before 保証
Happens-Before は、命令の並べ替えが Java の可視性保証を破壊することを防ぐための一連のルールです。
- volatile の happens-before 保証:volatile 変数への書き込みは、その後のその変数の読み取りより happens-before
- synchronized の happens-before 保証:ロックの解放は、その後の同じロックの取得より happens-before
Race Condition
2つ以上のスレッドが、不正な結果を引き起こす可能性のある方法で同じ変数にアクセスする場合、レースコンディションが発生します。
発生条件
- 2つ以上のスレッドが同じ変数またはデータを同時に読み書きする
- スレッドが以下のパターンで変数にアクセスする:
- Check-then-act:確認後実行
- Read-modify-write:変更値が以前読み取った値に依存
- スレッドの変数アクセスがアトミックではない
解決策
synchronized ブロックを使用して相互排他を確保:
CheckThenAct.java
synchronized (lock) {
if (condition) {
// 操作を実行
}
}