鎖的類型

鎖的分類

Java 並發程式設計中的各種鎖類型:

類型說明
悲觀鎖 (Pessimistic Lock)認為一定會有競爭,先加鎖
樂觀鎖 (Optimistic Lock)認為通常不會有競爭,更新時檢查
公平鎖 (Fair Lock)按照請求順序獲取鎖
非公平鎖 (Unfair Lock)不保證請求順序
可重入鎖 (Reentrant Lock)同一執行緒可多次獲取同一把鎖
自旋鎖 (Spin Lock)循環等待獲取鎖
獨佔鎖 (Exclusive Lock)一次只能有一個執行緒持有
讀寫鎖 (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 介面
它可以確保在同一時間只有一個執行緒可以訪問共享資源。當一個執行緒獲得了鎖之後,其他執行緒必須等待鎖被釋放才能繼續執行。

樂觀鎖 (Optimistic Lock)

適用於讀多寫少的場景。

實現方式:

  • Version 版本號機制
  • Timestamps 時間戳
  • CAS AlgorithmAtomic 類遞增操作是以此演算法實作的
CASExample.java
AtomicInteger atomicInt = new AtomicInteger();
atomicInt.incrementAndGet();
只有在更新資料時去判斷,如果資料沒被更新,則當前 Thread 將修改資料成功寫入。如果這個資料已經被其他 Thread 更新,則根據不同實現方式執行不同的操作(如放棄修改、重試)。

ReentrantLock

可重入鎖,提供比 synchronized 更靈活的控制能力。

  • 預設採用非公平鎖
  • synchronized => 隱式鎖
  • Lock => 顯式鎖
程式發生異常時,如沒有主動 unlock() 釋放鎖,則很可能造成 Deadlock,因此需要在 finally{} 中釋放鎖。
ReentrantLockExample.java
Lock lock = new ReentrantLock();
try {
    lock.lock();
    // 臨界區操作
} finally {
    lock.unlock();
}

公平鎖 vs 非公平鎖

類型特點
公平鎖效率較低,但保證順序
非公平鎖效率較高,可能造成 Thread Starvation

ReadWriteLock 讀寫鎖

一個資源可以被多個執行緒 Read Access,或者可以被一個執行緒 Write Access,但是不能同時存在 Read 和 Write Threads。

  • 讀讀共享
  • 讀寫互斥
  • 寫寫互斥

寫鎖降級為讀鎖 (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 死鎖

當兩個或多個執行緒相互等待對方持有的資源時,就會發生死鎖。

  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 Process ID:

jps

類似 Linux 的 ps -ef 命令。

獲取執行緒堆疊追蹤:

jstack <PID> > thread_dump.txt

JVM 自帶的 stack 追蹤工具,可以用來分析死鎖情況。

Monitor 管程

  • JVM version 3
  • 物件都有一個 Monitor,每次只有一個 Thread 進入獨佔的鎖
同步機制,保證同一時間只有一個 Thread 去 access data 或 code。JVM 同步基於 entry 和 quit,使用 monitor object 來實現。

參考資源