併發程式設計 —— 原始碼分析公平鎖和非公平鎖

莫那·魯道發表於2019-03-04

前言

ReentrantLock 提供了公平鎖和非公平鎖,只需要在構造方法中使用一個 boolean 引數即可。預設非公平鎖。

今天從原始碼層面看看區別和具體實現。

1. 類 UML 圖

image.png

ReentrantLock 內部有一個抽象類 Sync,繼承了 AQS。

而公平鎖的實現就是 FairSync,非公平鎖的實現就是 NodFairSync

兩把鎖的區別在於lock 方法的實現。

2. 公平鎖 lock 方法實現

final void lock() {
    acquire(1);
}
複製程式碼

呼叫的是 AQS 的acquire方法,熟悉 AQS 的同學都知道,AQS 會回撥子類的 tryAcquire 方法,看看公平鎖的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()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
複製程式碼

說下邏輯:

  1. 獲取 state 變數,如果是 0,說明鎖可以獲取。
  2. 判斷 AQS 佇列中是否有等待的執行緒,如果沒有,就使用 CAS 嘗試獲取。獲取成功後,將 CLH 的持有執行緒修改為當前執行緒。
  3. 重入鎖邏輯。
  4. 如果失敗,返回 false, AQS 會將這個執行緒放進佇列,並掛起。

注意上面的第二步:判斷 AQS 佇列中是否有等待的執行緒

這就是公平的體現。

再看看非公平鎖的區別。

3. 非公平鎖 lock 方法實現

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
複製程式碼

lock 方法就不同了,很衝動的一個方法,直接使用 CAS 獲取鎖,如果成功了,就設定鎖的持有執行緒為自己。很快速。所以預設使用了非公平鎖。

如果失敗了,就呼叫 AQS 的 acquire 方法。當然,我們看的還是tryAcquire方法,在上面的程式碼中,tryAcquire方法呼叫了父類SyncnonfairTryAcquire,為什麼在父類中呢?

ReentrantLocktryLock方法中,也呼叫了該方法。因為這個方法是快速返回的。該方法不會讓等待時間久的執行緒獲取鎖。符合 tryLock 的設計。

方法實現如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
複製程式碼

該方法相比較公平鎖的 tryAcquire方法,少了一步判斷 AQS 佇列中是否有等待的執行緒的操作。

他要做的就是直接搶鎖,不讓給佇列裡那些等待時間長的。

搶不到再進入佇列。等待他的前置節點喚醒他。這個過程是公平的。

4. 總結

ReentrantLock中的公平鎖和非公平鎖的區別就在於:呼叫 lock 方法獲取鎖的時候 要不要判斷 AQS 佇列中是否有等待的執行緒,公平鎖為了讓每一個執行緒都均衡的使用鎖,就需要判斷,如果有,讓給他,非公平鎖很霸道,不讓不讓就不讓。

但如果失敗了,進入佇列了,進會按照 AQS 的邏輯來,整體順序就是公平的。

還有個注意的地方就是:ReentrantLocktryLock(無超時機制) 方法使用的非公平策略。符合他的設計。

tryLock(long timeout, TimeUnit unit) 方法則會根據 Sync 的具體實現來呼叫。不會衝動的呼叫 nonfairTryAcquire 方法。

相關文章