可重入鎖:同一線程在持有鎖的情況下,再次獲取同一把鎖不會阻塞,計數器 +1;釋放時計數器 -1,歸零才真正釋放。
別名:遞歸鎖。
JDK 實現:synchronized、ReentrantLock均為可重入鎖。
public class ReentrantDemo implements Runnable {
public synchronized void outer() {
System.out.println(Thread.currentThread().getId() + " → outer");
inner(); // 同線程再次進入 synchronized 方法
}
public synchronized void inner() {
System.out.println(Thread.currentThread().getId() + " → inner");
}
@Override public void run() { outer(); }
public static void main(String[] args) {
ReentrantDemo demo = new ReentrantDemo();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
}
輸出:每個線程 ID 連續打印兩次,證明同線程可重入。
public class BadSpinLock {
private final AtomicReference owner = new AtomicReference<>();
public void lock() {
Thread cur = Thread.currentThread();
while (!owner.compareAndSet(null, cur)) { /* busy spin */ }
}
public void unlock() {
Thread cur = Thread.currentThread();
owner.compareAndSet(cur, null);
}
}
問題:
lock() → CAS 失敗 → 死循環 → 死鎖unlock() 直接釋放,忽略重入計數 → 邏輯錯誤
public class ReentrantSpinLock {
private final AtomicReference owner = new AtomicReference<>();
private int count = 0; // 重入計數
public void lock() {
Thread cur = Thread.currentThread();
if (cur == owner.get()) { // 已持有鎖
count++;
return;
}
while (!owner.compareAndSet(null, cur)) { /* spin */ }
}
public void unlock() {
Thread cur = Thread.currentThread();
if (cur == owner.get()) {
if (count != 0) {
count--; // 還有重入層數,僅遞減
} else {
owner.compareAndSet(cur, null); // 真正釋放
}
}
}
}
現在支持同線程重入、計數釋放,避免了死鎖與過早釋放。
// 簡化版 AQS 狀態機
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1)) // 首次獲取
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 進入 AQS 排隊
}
protected final boolean tryAcquire(int acquires) {
Thread cur = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(cur);
return true;
}
} else if (cur == getExclusiveOwnerThread()) { // 可重入判斷
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
關鍵點:getExclusiveOwnerThread() == currentThread 即為可重入條件,state 記錄重入次數。
| 場景 | 推薦鎖 | 理由 |
|---|---|---|
| 簡單同步、代碼塊短 | synchronized | JVM 優化深(偏向鎖、輕量級鎖、鎖消除)、可讀性高 |
| 需要公平性、可中斷、超時、Condition | ReentrantLock | 功能豐富、API 靈活 |
| 高併發讀多寫少 | ReentrantReadWriteLock | 讀共享、寫獨佔 |
| 微秒級極短臨界區、無阻塞需求 | 自旋鎖 / StampedLock | 避免內核態切換開銷 |
String/包裝類 → 常量池導致非預期共享synchronized(this) 公開方法 → 外部可鎖同一實例finally 中 unlock() → 死鎖隱患Lock 時:lock.lock(); try { ... } finally { lock.unlock(); }可重入鎖通過「線程所有權 + 計數器」解決同線程遞歸調用死鎖問題。synchronized 與 ReentrantLock 皆採用此機制。理解其原理,能幫助你在自定義鎖、排查死鎖、選型鎖策略時做出正確決策。