ReentrantLock基於AQS的公平鎖和非公平鎖的實現區別

阿阿sa發表於2021-09-17

ReentrantLock鎖的實現是基於AQS實現的,所以先簡單說下AQS:

AQS是AbstractQueuedSynchronizer縮寫,顧名思義:抽象的佇列同步器,它是JUC裡面許多同步工具類實現的核心

其實簡單來說AQS有兩個核心,一個是volatile修飾的int型別state,這個是記錄處於等待中需要持有鎖和正在持有鎖的執行緒數量

/**
 * The synchronization state.
 */
private volatile int state;

第二個就是Node內部類,他是AQS裡面FIFO雙向佇列節點的實現,他的一些屬性如下:

static final class Node {
     volatile int waitStatus;//等待狀態 volatile Node prev;//前驅節點 volatile Node next;//後繼節點 volatile Thread thread;//申請同步狀態的執行緒 Node nextWaiter;//等待節點的後繼節點(後續出AQS詳細篇再細講) }

這種結構可以從任意的一個節點開始很方便的訪問前驅和後繼節點。每個Node由執行緒封裝,當執行緒競爭失敗後會加入到AQS佇列中去。

基於這些再繼續聊下ReentrantLock的公平和非公平鎖的實現

ReentrantLock鎖的實現基於AQS,如下sync抽象內部類:

abstract static class Sync extends AbstractQueuedSynchronizer{
  abstract void lock(); 
}

Sync的子類有兩個:FairSync和NonfairSync(公平和非公平鎖的具體實現類),兩個鎖的不同就在於兩個方法的實現不同lock():加鎖的方法,還有tryAcquire(int acquires):嘗試持有鎖的方法,獲取成功返回true,失敗返回false

看看兩把鎖的原始碼實現:

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            acquire(1);
        }
        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;
        }
    }

  

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

nonfairTryAcquire方法是在父類Sync中定義:

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

可以看見,NonfairSync在使用lock()加鎖的時候就已經體現了非公平性了,因為lock()加鎖的時候直接嘗試使用CAS獲取鎖,如果獲取到了就不會入等待佇列,所以會有後來的執行緒先搶佔到鎖;

那如果沒有搶佔到鎖呢?會走else的acquire(1)方法。

先看看acquire方法的定義:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

這裡會發現acquire呼叫了tryAcquire方法,而在上面兩把鎖的原始碼中各自重寫了tryAcquire方法

這個方法中當getState()獲取同步狀態為0(沒有執行緒持有鎖)時候,繼續使用用CAS獲取鎖

兩個重寫的方法唯一不同之處在於,公平鎖加了hasQueuedPredecessors()方法:

public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

這個方法主要是用來判斷執行緒需不需要排隊,因為佇列是FIFO的,所以需要判斷佇列中有沒有相關執行緒的節點已經在排隊了。有則返回true表示執行緒需要排隊,沒有則返回false則表示執行緒無需排隊。

而非公平鎖就沒有判斷FIFO佇列是否還有排隊。

小結:

ReentrantLock中公平鎖和非公平鎖的實現主要有兩處區別:

1.在lock()方法中,非公平鎖一進來就嘗試CAS獲取鎖,不會管等待佇列裡面是否有等待執行緒

2.在tryAcquire方法中,判斷state等於0(沒有執行緒持有鎖的情況)後,公平鎖會先呼叫hasQueuedPredecessors()方法判斷FIFO佇列是否有等待執行緒,沒有才繼續嘗試獲取鎖,而非公平鎖是直接CAS獲取鎖

值得注意的是眾所周知ReentrantLock是可重入鎖:

不管是公平鎖還是非公平鎖,他們重寫的嘗試搶佔鎖的tryAcquire方法裡面都有這樣一段程式碼

else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
    }

 這個是判斷state不等於0(有執行緒持有鎖或者等待執行緒時候)之後,繼續判斷當前持有鎖的執行緒是不是等於當前申請鎖的執行緒,如果等於,直接返回true並且state+1,所以當前執行緒不用等待繼續持有鎖;這就完成了可重入的特性!

相關文章