ReentrantLock學習(三)公平鎖與非公平鎖

一、概念理解

公平鎖:申請所的時候排隊,誰也不插隊

非公平鎖:申請的時候插隊(先插隊,不行了再排隊)

 

二、差別

ReentrantLock的公平鎖與非公平鎖的差別在於,內部的同步器不一樣,lock()方法調用的是sync的lock(),分別是FairSync與NonfairSync。

sync的lock()內部調用了AQS的acquire,而AQS的acquire做了三件事,第一件是直接申請鎖(tryAcquire),第二件是沒申請下鎖的話,就創建線程等待節點並添加到等待隊列(addWaiter),第三件是將改節點的prev節點標記爲SIGNAL,並掛起該線程,等待喚醒再申請鎖(acquireQueued)。

FairSync與NonfairSync的區別在tryAcquire方法不同,如下代碼及註釋(文末有lock流程圖,一眼看差別

公平鎖同步器

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
        
    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //狀態等於0,鎖沒有佔用
        if (c == 0) {
            //當前隊列無等待線程 
            if (!hasQueuedPredecessors() &&
                //修改鎖狀態成功
                compareAndSetState(0, acquires)) {
                //設置當前佔着鎖的線程爲當前線程
                setExclusiveOwnerThread(current);
                //獲取鎖成功
                return true;
            }
        }
        //狀態不爲0,鎖被佔用,佔用鎖的線程是當前線程(重入)
        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;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        //直接修改鎖狀態爲1,也就是先嚐試申請一下看看能否成功
        if (compareAndSetState(0, 1))
            //設置當前佔着鎖的線程爲當前線程,申請鎖成功
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //acquire方法會調用tryAcquire
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //狀態等於0,鎖沒有佔用
    if (c == 0) {
        //修改鎖狀態爲1
        if (compareAndSetState(0, acquires)) {
            //設置當前佔着鎖的線程爲當前線程,申請鎖成功
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //狀態不爲0,鎖被佔用,佔用鎖的線程是當前線程(重入)
    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;
}

ps:hasQueuedPredecessors方法

((s = h.next) == null || s.thread != Thread.currentThread())  是爲了過濾掉兩種臨界情況

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    //h != t 這是必要條件
    return h != t &&
        //h.next == null 說明head存在,tail還未設置,該種情況出現在addWaiter添加第一個等待節點時出現,設置完head還未來得及設置tail
        //s.thread != Thread.currentThread() 該情況是防止,當前線程剛被unpark,首次執行tryAcquire時,head還未替換成當前線程節點
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

 

三、 白話總結

  1. 公平鎖在申請的時候先看有線程在排隊沒,有的話就去排隊,沒有的話就申請鎖(所有申請鎖線程都滿足FIFO)。
  2. 非公平鎖在申請的時候,先插隊看看(也就是直接申請鎖),可以的話就申請成功,不行的話再去排隊,排上隊的線程獲取鎖順序就不會變了(排上隊的線程滿足FIFO)。

附件: 

非公平鎖 lock流程圖

非公平鎖lock流程圖
非公平鎖lock流程圖

 

公平鎖 lock流程圖

公平鎖lock流程圖
公平鎖lock流程圖

 

 

unlock流程

unlock流程圖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章