Java 線程六大狀態:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。阻塞鎖使線程從 RUNNABLE → BLOCKED/WAITING,喚醒後回 RUNNABLE 參與調度。
| 機制 | 關鍵 API | 適用層級 | 特點 |
| synchronized | 監視器鎖 (Object Monitor) | JVM 內建 | 重量級鎖 → 鎖升級(偏向→輕量→重量) |
| ReentrantLock | AQS (AbstractQueuedSynchronizer) | JUC | 公平/非公平、可中斷、條件變數 |
| Object.wait/notify | 監視器等待集合 | JVM 內建 | 需在 synchronized 區塊內使用 |
| LockSupport.park/unpark | 線程阻塞/喚醒原語 | JUC 基礎設施 | 不需鎖對象、可指定阻塞器、支援許可證語義 |
CLH 佇列鎖:自旋轉阻塞的經典演進
CLH (Craig, Landin, Hagersten) 鎖以隱式佇列節點實現公平鎖,每個節點僅自旋等待前驅節點釋放。Java 版本常結合 LockSupport 將自旋改為阻塞:
public class CLHLock {
static class Node {
volatile Thread thread; // 當前持有鎖的線程
}
private final ThreadLocal myNode = ThreadLocal.withInitial(Node::new);
private final AtomicReferenceFieldUpdater tailUpdater =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tail");
@SuppressWarnings("unused") private volatile Node tail;
public void lock() {
Node node = myNode.get();
Node pred = tailUpdater.getAndSet(this, node); // 入隊
if (pred != null) {
pred.thread = Thread.currentThread(); // 通知前驅喚醒我
LockSupport.park(this); // 阻塞等待
pred.thread = null;
}
}
public void unlock() {
Node node = myNode.get();
if (!tailUpdater.compareAndSet(this, node, null)) { // 隊列還有後繼
LockSupport.unpark(node.thread); // 喚醒後繼
}
myNode.remove(); // 清理 ThreadLocal
}
}
關鍵設計點
- ThreadLocal 節點:每線程固定節點,避免重複分配
- AtomicReferenceFieldUpdater:無鎖 CAS 更新尾指標
- 前驅通知模式:前驅節點持有後繼線程引用,釋放時直接
unpark
- 許可證語義:
park/unpark 不會丟失喚醒信號(許可證最多 1),優於 wait/notify
阻塞鎖 vs 自旋鎖:效能抉擇
| 場景 | 推薦鎖類型 | 理由 |
| 關鍵區極短(< 1μs)、低競爭 | 自旋鎖 / CAS | 避免上下文切換開銷 |
| 關鍵區較長、高競爭 | 阻塞鎖 | 釋放 CPU、避免總線風暴 |
| 需公平性、條件變數 | ReentrantLock | AQS 佇列天然公平、支援 Condition |
| 簡單同步、程式碼簡潔 | synchronized | JVM 優化成熟、偏向鎖適合單線程 |
實戰建議
- 優先使用高層抽象:
ReentrantLock、ConcurrentHashMap、LongAdder 等 JUC 組件
- 避免手寫底層鎖:除非造輪子或極致效能調優,否則 CLH、MCS 等留給框架作者
- 監控鎖競爭:
jstack 分析 BLOCKED 線程、Java Flight Recorder 記錄鎖事件
- 鎖粒度與拆分:縮小關鍵區、分段鎖、無鎖資料結構(CAS、StampedLock)
總結
阻塞鎖透過 LockSupport.park/unpark 實現線程掛起與喚醒,是 Java 併發套件的基石。理解其原理有助於診斷死鎖、活鎖、優化高競爭場景。日常開發請善用 JUC 提供的成熟組件,僅在框架開發或極端效能場景深入底層。
- 上一篇文章: Android 圖片資源轉換完全手冊:Drawable、Bitmap、byte[] 互轉
- 下一篇文章: Java 函數式程式設計入門(一):Lambda 表達式與 Stream API 核心思想
Copyright ©
程式碼之家 All Rights Reserved