鎖是並發編程的基石。Java 提供了從內置鎖 synchronized 到顯式鎖 Lock 體系的完整工具箱。本文系統梳理鎖的分類、核心實現原理、關鍵特性對比,並給出生產環境選型指南。
| 維度 | 內置鎖 synchronized | 顯式鎖 Lock (JUC) |
|---|---|---|
| 實現層級 | JVM 原生 (monitorenter/monitorexit) | Java 代碼 (AQS + CAS) |
| 釋放方式 | 自動 (塊結束/異常) | 手動 unlock() (需 finally) |
| 可中斷 | 不可中斷 | lockInterruptibly() 支持 |
| 超時獲取 | 不支持 | tryLock(timeout) 支持 |
| 公平性 | 非公平 | 可選公平/非公平 |
| 條件變量 | wait/notify (單條件) | newCondition() (多條件) |
| 性能 (低爭用) | 優化後極佳 (偏向鎖/輕量級鎖) | 略高開銷 |
| 性能 (高爭用) | 較差 | 優秀 (CAS + 自旋 + 鎖升級) |
public class SyncDemo {
private static final Object LOCK = new Object();
// 1. 實例方法鎖:鎖 this
public synchronized void instanceMethod() { ... }
// 2. 靜態方法鎖:鎖 Class 對象
public synchronized static void staticMethod() { ... }
// 3. 代碼塊鎖:鎖任意對象(粒度最細)
public void blockSync() {
synchronized (LOCK) { ... } // 推薦:私有 final Object
// synchronized (this) { ... } // 等同實例方法鎖
}
}
同一線程可重複獲取同一鎖。JVM 維護 計數器 + 持有線程:
這使得遞歸調用、父子類同步方法調用不會死鎖。
無鎖 → 偏向鎖 → 輕量級鎖 (CAS 自旋) → 重量級鎖 (OS Mutex)。
-XX:-UseBiasedLocking 可禁用偏向鎖。
private final Lock lock = new ReentrantLock(); // 非公平(默認)
// private final Lock lock = new ReentrantLock(true); // 公平
public void doWork() {
lock.lock(); // 必須在 try 外獲取,防止拋異常未釋放
try {
// 臨界區
} finally {
lock.unlock(); // 必須在 finally 中釋放
}
}
tryLock():非阻塞嘗試獲取,立即返回 true/falsetryLock(timeout, unit):限時等待,避免無限阻塞lockInterruptibly():可響應中斷的獲取,拋出 InterruptedExceptionprivate final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
// 生產者
lock.lock();
try {
while (queue.size() == capacity) notFull.await();
queue.add(item);
notEmpty.signal();
} finally { lock.unlock(); }
// 消費者
lock.lock();
try {
while (queue.isEmpty()) notEmpty.await();
Item item = queue.remove();
notFull.signal();
return item;
} finally { lock.unlock(); }
一把鎖可關聯多個 Condition,實現精確通知(signalAll 喚醒所有)。
適用「讀多寫少」場景:讀讀並發、讀寫互斥、寫寫互斥。
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 讀操作
readLock.lock();
try { return data; } finally { readLock.unlock(); }
// 寫操作
writeLock.lock();
try { data = newValue; } finally { writeLock.unlock(); }
state 高 16 位存讀鎖計數,低 16 位存寫鎖計數進一步優化讀多寫少:無鎖樂觀讀 + 版本戳驗證。
private final StampedLock sl = new StampedLock();
// 樂觀讀(極快,無鎖)
public double readOptimistic() {
long stamp = sl.tryOptimisticRead();
double x = this.x, y = this.y; // 讀取共享變量
if (!sl.validate(stamp)) { // 驗證期間無寫入
// 降級為悲觀讀
stamp = sl.readLock();
try {
x = this.x; y = this.y;
} finally { sl.unlockRead(stamp); }
}
return Math.sqrt(x*x + y*y);
}
// 悲觀讀
public double readPessimistic() {
long stamp = sl.readLock();
try { return Math.sqrt(x*x + y*y); }
finally { sl.unlockRead(stamp); }
}
// 寫操作
public void write(double x, double y) {
long stamp = sl.writeLock();
try { this.x = x; this.y = y; }
finally { sl.unlockWrite(stamp); }
}
注意:StampedLock 不可重入,不支持 Condition,慎用於複雜邏輯。
| 工具 | 核心用途 | 關鍵方法 |
|---|---|---|
| CountDownLatch | 一個或多個線程等待 N 個事件完成 | countDown(), await() |
| CyclicBarrier | N 個線程相互等待,達到屏障點同時繼續 | await() |
| Semaphore | 控制同時訪問資源的線程數 (許可證) | acquire(), release() |
| Exchanger | 兩線程交換數據 | exchange(V) |
synchronized (JVM 優化最好,代碼最簡潔)ReentrantLockReentrantReadWriteLockStampedLockCountDownLatch/CyclicBarrierSemaphoreLock 必須 lock() 在 try 外,unlock() 在 finally 中ReentrantReadWriteLock 寫鎖降級讀鎖模式要熟練掌握