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

Java 多線程核心:ReentrantLock 重入鎖深度解析與實戰

     編輯:Java 相關知識

從 synchronized 到 Lock 的演進

JDK 1.5 前,synchronized 是唯一同步手段,存在局限:

  • 隱式鎖獲取/釋放,必須在同一代碼塊結構中
  • 無法中斷等待、無法超時控制、無法公平性配置
  • 單一條件等待(wait/notify),不支持多條件變量

java.util.concurrent.locks.Lock 接口及實現類 ReentrantLock 提供顯式鎖機制,解決上述痛點。

ReentrantLock 核心特性

特性說明
可重入性同一線程可多次獲取鎖(計數器累加),需同次數釋放
公平/非公平構造器 new ReentrantLock(true) 設置公平鎖(FIFO),默認非公平(吞吐優先)
可中斷獲取lockInterruptibly() 支持線程中斷響應
超時獲取tryLock(timeout, unit) 避免無限等待
多條件變量newCondition() 創建多個 Condition,精準通知
監控診斷提供 getHoldCount()、getQueuedThreads() 等檢測 API

核心 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 導致數據競爭,結果不可預期(如庫存變負數)。

版本三:Condition 實現有界緩衝區(完整解決方案)

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

公平鎖 vs 非公平鎖選擇指南

維度公平鎖非公平鎖(默認)
獲取機制嚴格 FIFO 隊列順序搶佔式,新請求可插隊
吞吐量較低(上下文切換多)較高(減少線程唤醒/掛起)
饑餓風險無理論存在(極少見)
適用場景嚴格順序要求、長耗時臨界區高併發、短耗時臨界區(大多數場景)

最佳實踐棧單

  1. 必須在 finally 中 unlock:防止異常導致鎖永不釋放,造成死鎖
  2. 鎖粒度最小化:僅鎖護共享狀態操作,避免在鎖內執行 I/O、睡眠、耗時計算
  3. 優先 tryLock 超時:避免業務線程無限阻塞,支持降級/熔斷邏輯
  4. Condition 替代 wait/notify:綁定顯式鎖,支持多條件精準通知,語義更清晰
  5. 公平鎖謹慎使用:除非業務強要求順序,默認非公平鎖性能更優
  6. 避免嵌套不同鎖:若必須嵌套,全局固定獲取順序,防止死鎖

對比總結:synchronized vs ReentrantLock

特性synchronizedReentrantLock
鎖類型隱式監視器鎖顯式鎖
釋放方式自動(代碼塊結束)手動 unlock()
可中斷否是 lockInterruptibly()
超時獲取否是 tryLock(timeout)
公平性非公平(JVM 優化)可配置
條件變量單一 wait/notify多個 Condition
性能(高併發)JDK 8+ 優化後接近略優/持平
適用建議簡單同步、方法級鎖複雜同步邏輯、需高級特性

總結

ReentrantLock 是 Java 並發編程的利器,提供可重入、可中斷、可超時、公平可選、多條件的顯式鎖能力。掌握其核心 API 與 Condition 配合模式,能構建高性能、可維護的並發組件。實戰中遵循「最小粒度、必然釋放、超時守護」原則,發揮其最大價值。

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