鎖的類型
鎖的分類
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 Algorithm:
Atomic類遞增操作是以此演算法實作的
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
死鎖原因
- 資源不足
- Process/Thread 順序不當
- 資源分配不當
死鎖檢測
使用 JVM 工具檢測死鎖:
查找 Java Process ID:
jps類似 Linux 的 ps -ef 命令。
獲取執行緒堆疊追蹤:
jstack <PID> > thread_dump.txtJVM 自帶的 stack 追蹤工具,可以用來分析死鎖情況。
Monitor 管程
- JVM version 3
- 物件都有一個 Monitor,每次只有一個 Thread 進入獨佔的鎖
同步機制,保證同一時間只有一個 Thread 去 access data 或 code。JVM 同步基於 entry 和 quit,使用 monitor object 來實現。