首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 鎖機制深度解析:可重入鎖原理、死鎖避免與自旋鎖改造實戰

     編輯:Java 相關知識

什麼是可重入鎖

可重入鎖:同一線程在持有鎖的情況下,再次獲取同一把鎖不會阻塞,計數器 +1;釋放時計數器 -1,歸零才真正釋放。
別名:遞歸鎖。
JDK 實現:synchronized、ReentrantLock 均為可重入鎖。

synchronized 可重入演示


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);
    }
}

問題:

  1. 同線程二次 lock() → CAS 失敗 → 死循環 → 死鎖
  2. 首次 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);  // 真正釋放
            }
        }
    }
}

現在支持同線程重入、計數釋放,避免了死鎖與過早釋放。

ReentrantLock 核心源碼邏輯


// 簡化版 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 記錄重入次數。

生產環境最佳實踐

場景推薦鎖理由
簡單同步、代碼塊短synchronizedJVM 優化深(偏向鎖、輕量級鎖、鎖消除)、可讀性高
需要公平性、可中斷、超時、ConditionReentrantLock功能豐富、API 靈活
高併發讀多寫少ReentrantReadWriteLock讀共享、寫獨佔
微秒級極短臨界區、無阻塞需求自旋鎖 / StampedLock避免內核態切換開銷

避坑清單

  • ❌ 鎖對象用 String/包裝類 → 常量池導致非預期共享
  • ❌ synchronized(this) 公開方法 → 外部可鎖同一實例
  • ❌ 忘記 finally 中 unlock() → 死鎖隱患
  • ✅ 使用 Lock 時:lock.lock(); try { ... } finally { lock.unlock(); }

總結

可重入鎖通過「線程所有權 + 計數器」解決同線程遞歸調用死鎖問題。synchronized 與 ReentrantLock 皆採用此機制。理解其原理,能幫助你在自定義鎖、排查死鎖、選型鎖策略時做出正確決策。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved