ロックの種類
ロックの分類
Java 並行プログラミングにおける様々なロック種類:
| 種類 | 説明 |
|---|---|
| 悲観的ロック (Pessimistic Lock) | 競合が必ず発生すると想定し、先にロック |
| 楽観的ロック (Optimistic Lock) | 通常競合しないと想定し、更新時にチェック |
| 公平ロック (Fair Lock) | 要求順序でロックを取得 |
| 非公平ロック (Unfair Lock) | 要求順序を保証しない |
| 再入可能ロック (Reentrant Lock) | 同じスレッドが同じロックを複数回取得可能 |
| スピンロック (Spin Lock) | ロック取得まで循環待機 |
| 排他ロック (Exclusive Lock) | 一度に1つのスレッドのみ保持可能 |
| 読み書きロック (ReadWrite Lock) | 読み共有、書き排他 |
| 偏向ロック (Biased Lock) | 最初に取得したスレッドに偏向 |
| 軽量ロック (Lightweight Lock) | CAS 操作でロックを競合 |
| 重量ロック (Heavyweight Lock) | ブロッキングでロック取得を待機 |
| スタンプロック (Stamped Lock) | 楽観的読み取りロック、JDK 8 で導入 |
ロックアップグレードパス
flowchart LR
A[ロックなし] --> B[偏向ロック]
B --> C[軽量ロック]
C --> D[重量ロック]
別のアップグレードパス:
flowchart LR
A[ロックなし] --> B[排他ロック]
B --> C[読み書きロック]
C --> D[スタンプロック]
悲観的ロック (Pessimistic Lock)
書き込み操作が多い場合に適しています。先にロックすることで Write Operation 時のデータの正確性を保証します。
実装方法:
synchronizedキーワードLockインターフェースの実装
同時に1つのスレッドのみが共有リソースにアクセスできることを保証します。あるスレッドがロックを取得すると、他のスレッドはロックが解放されるまで待機する必要があります。
楽観的ロック (Optimistic Lock)
読み取りが多く書き込みが少ない場面に適しています。
実装方法:
- Version バージョン番号メカニズム
- Timestamps タイムスタンプ
- CAS Algorithm:
Atomicクラスのインクリメント操作はこのアルゴリズムで実装されています
CASExample.java
AtomicInteger atomicInt = new AtomicInteger();
atomicInt.incrementAndGet();データを更新する時にのみ判定します。データが更新されていなければ、現在のスレッドが変更を正常に書き込みます。すでに他のスレッドによって更新されている場合は、実装に応じて異なる操作(変更の放棄、リトライなど)を実行します。
ReentrantLock
再入可能ロックで、synchronized よりも柔軟な制御機能を提供します。
- デフォルトは非公平ロック
synchronized=> 暗黙的ロックLock=> 明示的ロック
プログラムで例外が発生した際、主動的に
unlock() でロックを解放しないと、デッドロックが発生する可能性があります。そのため finally{} でロックを解放する必要があります。ReentrantLockExample.java
Lock lock = new ReentrantLock();
try {
lock.lock();
// クリティカルセクション操作
} finally {
lock.unlock();
}公平ロック vs 非公平ロック
| 種類 | 特徴 |
|---|---|
| 公平ロック | 効率は低いが、順序を保証 |
| 非公平ロック | 効率は高いが、Thread Starvation が発生する可能性 |
ReadWriteLock 読み書きロック
1つのリソースは複数のスレッドから Read アクセス、または1つのスレッドから Write アクセスできますが、同時に Read と Write のスレッドは存在できません。
- 読み読み共有
- 読み書き排他
- 書き書き排他
書きロックから読みロックへのダウングレード (JDK 8)
flowchart LR
GWL[書きロック取得] --> GRL[読みロック取得]
GRL --> RWL[書きロック解放]
RWL --> RRL[読みロック解放]
読みロックから書きロックへのアップグレードはできません。
ロックメカニズムまとめ
| 種類 | 読み読み | 読み書き | 書き書き |
|---|---|---|---|
| synchronized/ReentrantLock | 排他 | 排他 | 排他 |
| ReentrantReadWriteLock | 共有 | 排他 | 排他 |
ReentrantReadWriteLock の欠点:
- Lock Starvation:読み取りが続き、書き込み操作がない
- 読み取り中は書き込めない、読み取りが完了してから書き込み可能
偏向ロック (Biased Lock)
- JDK 1.6 で導入
- JDK 15 で非推奨
JVM パラメータ:
-XX:BiasedLockingStartupDelay=0 # プログラム起動時にすぐに偏向ロックを有効化デッドロック (Deadlock)
2つ以上のスレッドが互いに相手が保持しているリソースを待っている場合、デッドロックが発生します。
flowchart TB
A[Thread A]
B[Thread B]
LA((Lock A))
LB((Lock B))
A -->|保持| LA
A -->|取得試行| LB
B -->|保持| LB
B -->|取得試行| LA
デッドロックの原因
- リソース不足
- Process/Thread の順序不適切
- リソース割り当て不適切
デッドロック検出
JVM ツールでデッドロックを検出:
Java プロセス ID を検索:
jpsLinux の ps -ef コマンドに類似。
スレッドスタックトレースを取得:
jstack <PID> > thread_dump.txtJVM 内蔵のスタックトレースツールで、デッドロック状況を分析できます。
Monitor 管程
- JVM version 3
- すべてのオブジェクトには Monitor があり、一度に1つのスレッドのみが排他的なロックに入ることができます
同期メカニズムで、同時に1つのスレッドのみがデータやコードにアクセスできることを保証します。JVM の同期は entry と quit に基づき、monitor object を使用して実装されています。