鎖是並發程式設計中保護共享資料一致性的核心機制。Java 提供 synchronized、ReentrantLock 等內建鎖,但理解底層鎖原理 (自旋鎖、票據鎖、CLH 鎖、MCS 鎖) 對於高效能並發元件開發、JUC 原始碼閱讀、無鎖演算法設計至關重要。本文深度解析自旋鎖與三大變體的實作原理、適用場景與效能特徵。
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
// 持有鎖的線程引用,null 表示無鎖
private final AtomicReference owner = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
// CAS: 期望值 null → 設為 current
while (!owner.compareAndSet(null, current)) {
// 自旋等待,可加入 Thread.onSpinWait() (JDK 9+) 提示 CPU
Thread.onSpinWait();
}
}
public void unlock() {
Thread current = Thread.currentThread();
// 僅持有者能釋放:期望值 current → 設為 null
owner.compareAndSet(current, null);
}
}
ticketNum 發號器、serviceNum 服務號myTicket == serviceNumserviceNum++ 喚醒下一位serviceNum 觸發快取一致性流量import java.util.concurrent.atomic.AtomicInteger;
public class TicketLock {
private final AtomicInteger ticketNum = new AtomicInteger(0);
private final AtomicInteger serviceNum = new AtomicInteger(0);
private final ThreadLocal myTicket = new ThreadLocal<>();
public void lock() {
int ticket = ticketNum.getAndIncrement();
myTicket.set(ticket);
while (ticket != serviceNum.get()) {
Thread.onSpinWait();
}
}
public void unlock() {
int ticket = myTicket.get();
// CAS 確保只有持有者能遞增
serviceNum.compareAndSet(ticket, ticket + 1);
myTicket.remove();
}
}
CLHNode,透過 AtomicReferenceFieldUpdater 原子性插入佇列尾部isLocked 變為 falseisLocked = falseimport java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
public static class CLHNode {
volatile boolean isLocked = true; // 預設鎖定
}
@SuppressWarnings("unused")
private volatile CLHNode tail;
private static final ThreadLocal LOCAL = new ThreadLocal<>();
private static final AtomicReferenceFieldUpdater UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, CLHNode.class, "tail");
public void lock() {
CLHNode node = new CLHNode();
LOCAL.set(node);
CLHNode pred = UPDATER.getAndSet(this, node); // 原子性入隊
if (pred != null) {
while (pred.isLocked) { Thread.onSpinWait(); } // 自旋等待前驅釋放
pred = null; // 釋放前驅引用助 GC
}
}
public void unlock() {
CLHNode node = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false; // 非尾節點,直接釋放
}
LOCAL.remove();
}
}
next 指標,形成顯式鏈表isLocked (由後繼者設定)next.isLocked = false)import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
volatile MCSNode next;
volatile boolean isLocked = true;
}
@SuppressWarnings("unused")
private volatile MCSNode queue;
private static final ThreadLocal NODE = new ThreadLocal<>();
private static final AtomicReferenceFieldUpdater UPDATER =
AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "queue");
public void lock() {
MCSNode node = new MCSNode();
NODE.set(node);
MCSNode pred = UPDATER.getAndSet(this, node);
if (pred != null) {
pred.next = node; // 連接到前驅
while (node.isLocked) { Thread.onSpinWait(); } // 自旋等待自身釋放
}
}
public void unlock() {
MCSNode node = NODE.get();
if (node.next == null) {
// 可能是尾節點,嘗試 CAS 設為 null
if (UPDATER.compareAndSet(this, node, null)) {
return; // 成功,無後繼者
}
// 失敗:有後繼者正在設定 next,等待其完成
while (node.next == null) { Thread.onSpinWait(); }
}
// 有後繼者,直接喚醒
node.next.isLocked = false;
node.next = null;
NODE.remove();
}
}
| 鎖類型 | 公平性 | 記憶體佔用 | 快取友好度 | NUMA 適應性 | 適用場景 |
|---|---|---|---|---|---|
| 基礎自旋鎖 | 非公平 | 極低 (1 個引用) | 差 (所有 CPU 爭搶同一快取行) | 差 | 極短臨界區、低競爭 |
| 票據鎖 | 公平 (FIFO) | 低 (2 個整數) | 差 (所有 CPU 讀寫 serviceNum) | 差 | 需公平性、短臨界區 |
| CLH 鎖 | 公平 (FIFO) | 中 (每線程 1 節點) | 優 (讀取前驅節點,分散快取行) | 中 (需讀取遠端前驅) | 通用、中等競爭 |
| MCS 鎖 | 公平 (FIFO) | 中 (每線程 1 節點) | 優 (自旋本地變數) | 優 (本地自旋、遠端只寫入) | 高競爭、NUMA 系統 |
ReentrantLock (非公平/公平) 底層基於 CLH 變體 (Sync 佇列),但將自旋改為 阻塞 (LockSupport.park/unpark)StampedLock 讀寫鎖使用 CLH 佇列 + 樂觀讀LongAdder、ConcurrentHashMap 等使用 分段/分散式鎖 降低競爭synchronized (JVM 優化極好)、ReentrantLock (需可中斷/超時/公平/條件變數)