ロックの種類

ロックの分類

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 AlgorithmAtomic クラスのインクリメント操作そうさはこのアルゴリズムで実装じっそうされています
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

デッドロックの原因

  1. リソース不足ふそく
  2. Process/Thread の順序じゅんじょ不適切ふてきせつ
  3. リソース不適切ふてきせつ

デッドロック検出

JVM ツールでデッドロックを検出けんしゅつ

Java プロセス ID を検索けんさく

jps

Linux の ps -ef コマンドに類似るいじ

スレッドスタックトレースを取得しゅとく

jstack <PID> > thread_dump.txt

JVM 内蔵ないぞうのスタックトレースツールで、デッドロック状況じょうきょう分析ぶんせきできます。

Monitor 管程

  • JVM version 3
  • すべてのオブジェクトには Monitor があり、一度いちどに1つのスレッドのみが排他的はいたてきなロックにはいることができます
同期どうきメカニズムで、同時どうじに1つのスレッドのみがデータやコードにアクセスできることを保証ほしょうします。JVM の同期どうきは entry と quit にもとづき、monitor object を使用しようして実装じっそうされています。

参考リソース