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

Java 併發核心:ReentrantLock 公平鎖原理、使用姿勢與性能權衡

     編輯:Java 綜合教學

為什麼需要公平鎖?

Java 內置鎖 synchronized 及 ReentrantLock 默認模式均為非公平鎖:線程釋放鎖後,等待隊列中的線程與新請求鎖的線程競爭,可能導致線程飢餓——優先級低或遲到的線程長期無法獲取鎖。公平鎖嚴格按照請求順序 (FIFO) 授予鎖,消除飢餓,代價是吞吐量下降。

ReentrantLock 公平鎖構造與核心類結構

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
  • FairSync:繼承 Sync → AbstractQueuedSynchronizer (AQS),重寫 tryAcquire 增加 !hasQueuedPredecessors() 判斷。
  • NonfairSync:直接嘗試 CAS 獲取鎖,不檢查隊列前驅。

公平鎖獲取邏輯源碼解讀 (FairSync.tryAcquire)

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 關鍵差異:公平鎖必須檢查是否有前驅節點
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        // 重入邏輯
        setState(c + acquires);
        return true;
    }
    return false;
}

hasQueuedPredecessors() 檢查當前線程節點是否為隊列首節點(頭節點的下一個),若否則排隊等待,實現 FIFO。

完整 Demo:公平鎖交替執行驗證

public class FairLockDemo implements Runnable {
    // true 開啟公平鎖
    private static final ReentrantLock fairLock = new ReentrantLock(true);
    // private static final ReentrantLock unfairLock = new ReentrantLock(false); // 對比用

    @Override public void run() {
        for (int i = 0; i < 5; i++) {
            fairLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 獲得鎖 - 第 " + (i+1) + " 次");
                // 模擬業務耗時
                Thread.sleep(10);
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            finally { fairLock.unlock(); }
        }
    }

    public static void main(String[] args) {
        FairLockDemo demo = new FairLockDemo();
        new Thread(demo, "Thread-A").start();
        new Thread(demo, "Thread-B").start();
        new Thread(demo, "Thread-C").start();
    }
}

輸出分析 (公平鎖)

Thread-A 獲得鎖 - 第 1 次
Thread-B 獲得鎖 - 第 1 次
Thread-C 獲得鎖 - 第 1 次
Thread-A 獲得鎖 - 第 2 次
Thread-B 獲得鎖 - 第 2 次
Thread-C 獲得鎖 - 第 2 次
...

三個線程嚴格輪轉,無連續搶佔。若改為非公平鎖,輸出將呈現明顯的「批次式」搶佔(某線程連續多次)。

公平鎖 vs 非公平鎖:性能與場景對比

維度 公平鎖 (FairSync) 非公平鎖 (NonfairSync / synchronized)
吞吐量較低 (需維護隊列順序、頻繁上下文切換)高 (新線程可「插隊」獲取鎖,減少切換)
線程飢餓不可能極端情況下可能
響應時間均勻性極佳 (方差極小)波動大
適用場景對公平性敏感、執行時間較長的關鍵區 (如寫入操作、計費扣款)高併發、短耗時、追求極致吞吐 (如緩存讀取、計數器)

進階:公平鎖的條件變量 (Condition)

ReentrantLock fairLock = new ReentrantLock(true);
Condition notFull = fairLock.newCondition();
Condition notEmpty = fairLock.newCondition();

// 生產者
fairLock.lock();
try {
    while (queue.size() == capacity) notFull.await();
    queue.add(item);
    notEmpty.signal();
} finally { fairLock.unlock(); }

// 消費者
fairLock.lock();
try {
    while (queue.isEmpty()) notEmpty.await();
    Item item = queue.poll();
    notFull.signal();
    return item;
} finally { fairLock.unlock(); }

公平鎖確保等待隊列中的線程按順序被喚醒,避免「驚群效應」下的飢餓。

最佳實踐總結

  1. 默認選非公平鎖:new ReentrantLock() 或 synchronized,性能優勢明顯。
  2. 業務要求嚴格先來後到 (如訂單序號生成、限流令牌桶填充) 時,顯式 new ReentrantLock(true)。
  3. 避免長時間持有公平鎖:鎖內邏輯越簡單越好,耗時操作 (IO、RPC) 必須移出鎖外。
  4. 配合 tryLock 超時機制:fairLock.tryLock(5, TimeUnit.SECONDS) 防止死等。
  5. 監控指標:關注 LockSupport.parkNanos 時間、線程阻塞佇列長度,及時發現鎖爭用熱點。

總結

ReentrantLock 公平鎖基於 AQS 隊列實現 FIFO 授權,解決線程飢餓問題,但引入額外同步開銷導致吞吐量下降 20%-50%。工程上默認非公平,按需開啟公平,並結合 tryLock、Condition 構建高可用併發組件。

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