ReentrantLock 是 Java 並發包(JUC)中最基礎也最重要的鎖實現,它是一個可重入的互斥鎖,又稱「獨占鎖」。這意味著同一時間點僅允許一個線程持有該鎖,但同一線程可多次重複獲取鎖而不會發生死鎖。
ReentrantLock 提供兩種模式:
兩者底層皆基於 AQS(AbstractQueuedSynchronizer)的同步隊列實現,差別在於 tryAcquire 邏輯的判斷條件。
| 方法 | 說明 |
|---|---|
lock() | 獲取鎖,若被佔用則阻塞等待 |
lockInterruptibly() | 可響應中斷地獲取鎖 |
tryLock() | 嘗試非阻塞獲取鎖,立即返回結果 |
tryLock(timeout, unit) | 帶超時的鎖獲取 |
unlock() | 釋放鎖,必須在 finally 塊中調用 |
newCondition() | 創建關聯的 Condition 對象,實現精準線程通信 |
isFair() | 判斷是否為公平鎖 |
getHoldCount() | 當前線程重入次數 |
使用 lock()/unlock() 保護共享倉庫的生產/消費操作,確保線程安全:
public void produce(int val) {
lock.lock();
try {
size += val;
System.out.printf("%s produce(%d) --> size=%d%n",
Thread.currentThread().getName(), val, size);
} finally {
lock.unlock();
}
}
運行結果:最終庫存 50,符合預期。但存在兩個業務邏輯缺陷:庫存可能為負數、無容量上限。
註釋掉鎖操作後,多線程並發導致數據競爭,最終庫存出現 -60 等錯誤結果,直觀展示互斥鎖的必要性。
引入 Condition 實現「滿倉等待、空倉等待」機制,解決業務缺陷:
private Condition fullCondition = lock.newCondition(); // 生產者等待條件
private Condition emptyCondition = lock.newCondition(); // 消費者等待條件
public void produce(int val) {
lock.lock();
try {
int left = val;
while (left > 0) {
while (size >= capacity) fullCondition.await(); // 倉滿等待
int inc = Math.min(left, capacity - size);
size += inc; left -= inc;
emptyCondition.signal(); // 喚醒消費者
}
} finally { lock.unlock(); }
}
關鍵點:
while 迴圈防止虛假喚醒signal() 單點喚醒,配合精準條件變量finally 中釋放鎖,防止異常導致死鎖tryLock(timeout) 避免無限阻塞ReentrantLock 作為 JUC 基石,掌握其原理與用法是構建高性能並發系統的關鍵一步。