JDK 1.5 前,synchronized 是唯一同步手段,存在局限:
wait/notify),不支持多條件變量java.util.concurrent.locks.Lock 接口及實現類 ReentrantLock 提供顯式鎖機制,解決上述痛點。
| 特性 | 說明 |
|---|---|
| 可重入性 | 同一線程可多次獲取鎖(計數器累加),需同次數釋放 |
| 公平/非公平 | 構造器 new ReentrantLock(true) 設置公平鎖(FIFO),默認非公平(吞吐優先) |
| 可中斷獲取 | lockInterruptibly() 支持線程中斷響應 |
| 超時獲取 | tryLock(timeout, unit) 避免無限等待 |
| 多條件變量 | newCondition() 創建多個 Condition,精準通知 |
| 監控診斷 | 提供 getHoldCount()、getQueuedThreads() 等檢測 API |
// 創建
ReentrantLock lock = new ReentrantLock(); // 非公平(默認)
ReentrantLock fairLock = new ReentrantLock(true); // 公平
// 獲取鎖
lock.lock(); // 阻塞獲取,必須在 finally 中 unlock
lock.lockInterruptibly(); // 可中斷獲取
lock.tryLock(); // 非阻塞嘗試,立即返回結果
lock.tryLock(5, TimeUnit.SECONDS); // 超時獲取
// 釋放鎖
lock.unlock(); // 必須由持有線程調用,否則拋出 IllegalMonitorStateException
// 條件變量
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
notFull.await(); // 等待(釋放鎖,阻塞)
notFull.signal(); // 單個喚醒
notFull.signalAll(); // 喚醒所有
// 監控
lock.isHeldByCurrentThread(); // 當前線程是否持有鎖
lock.getHoldCount(); // 重入次數
lock.getQueueLength(); // 等待線程估計數
class Depot {
private int size = 0;
private final Lock lock = new ReentrantLock();
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(); // 必須在 finally 釋放
}
}
public void consume(int val) {
lock.lock();
try {
size -= val;
System.out.printf("%s consume(%d) <-- size=%d%n", Thread.currentThread().getName(), val, size);
} finally {
lock.unlock();
}
}
}
註釋掉 lock.lock()/unlock(),多線程並發修改 size 導致數據競爭,結果不可預期(如庫存變負數)。
class BoundedDepot {
private final int capacity;
private int size = 0;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 生產條件
private final Condition notEmpty = lock.newCondition(); // 消費條件
public BoundedDepot(int capacity) { this.capacity = capacity; }
public void produce(int val) throws InterruptedException {
lock.lock();
try {
int remaining = val;
while (remaining > 0) {
// 倉庫滿,等待消費
while (size >= capacity) notFull.await();
int inc = Math.min(remaining, capacity - size);
size += inc;
remaining -= inc;
System.out.printf("%s produce(%d) left=%d inc=%d size=%d%n",
Thread.currentThread().getName(), val, remaining, inc, size);
notEmpty.signal(); // 通知消費者
}
} finally {
lock.unlock();
}
}
public void consume(int val) throws InterruptedException {
lock.lock();
try {
int remaining = val;
while (remaining > 0) {
// 倉庫空,等待生產
while (size <= 0) notEmpty.await();
int dec = Math.min(remaining, size);
size -= dec;
remaining -= dec;
System.out.printf("%s consume(%d) left=%d dec=%d size=%d%n",
Thread.currentThread().getName(), val, remaining, dec, size);
notFull.signal(); // 通知生產者
}
} finally {
lock.unlock();
}
}
}
| 維度 | 公平鎖 | 非公平鎖(默認) |
|---|---|---|
| 獲取機制 | 嚴格 FIFO 隊列順序 | 搶佔式,新請求可插隊 |
| 吞吐量 | 較低(上下文切換多) | 較高(減少線程唤醒/掛起) |
| 饑餓風險 | 無 | 理論存在(極少見) |
| 適用場景 | 嚴格順序要求、長耗時臨界區 | 高併發、短耗時臨界區(大多數場景) |
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 鎖類型 | 隱式監視器鎖 | 顯式鎖 |
| 釋放方式 | 自動(代碼塊結束) | 手動 unlock() |
| 可中斷 | 否 | 是 lockInterruptibly() |
| 超時獲取 | 否 | 是 tryLock(timeout) |
| 公平性 | 非公平(JVM 優化) | 可配置 |
| 條件變量 | 單一 wait/notify | 多個 Condition |
| 性能(高併發) | JDK 8+ 優化後接近 | 略優/持平 |
| 適用建議 | 簡單同步、方法級鎖 | 複雜同步邏輯、需高級特性 |
ReentrantLock 是 Java 並發編程的利器,提供可重入、可中斷、可超時、公平可選、多條件的顯式鎖能力。掌握其核心 API 與 Condition 配合模式,能構建高性能、可維護的並發組件。實戰中遵循「最小粒度、必然釋放、超時守護」原則,發揮其最大價值。