公平鎖和非公平鎖-ReentrantLock是如何實(shí)現(xiàn)公平、非公平的

1、什么是公平鎖與非公平鎖

公平鎖:公平鎖就是保障了多線程下各線程獲取鎖的順序,先到的線程優(yōu)先獲取鎖。
非公平鎖:非公平鎖則無(wú)法提供這個(gè)保障(先到的線程優(yōu)先獲取鎖)。



2、ReentrantLock如何實(shí)現(xiàn)公平與非公平

Java并發(fā)包下面的ReentrantLock、ReadWriteLock默認(rèn)都是非公平模式。

下面我們就來(lái)一起看看ReentrantLock是如何實(shí)現(xiàn)公平與非公平的。

ReentrantLock實(shí)現(xiàn)了Lock接口。提供了下面2個(gè)構(gòu)造方法

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

默認(rèn)構(gòu)造的是非公平鎖NonfairSync。
NonfairSync和FairSync都是ReentrantLock的內(nèi)部類,且繼承ReentrantLock的內(nèi)部抽象類Sync。

// 公平鎖
protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 主要區(qū)別:有hasQueuedPredecessors方法
            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;
    }
}

// 非公平鎖
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
      // 主要區(qū)別:沒(méi)有hasQueuedPredecessors方法
        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;
}

二者的主要去別在于是否有hasQueuedPredecessors方法,我們看下hasQueuedPredecessors的源碼。該方法返回“隊(duì)列中是否存在一個(gè)線程(先于當(dāng)前線程)”,如果存在話,當(dāng)前線程就要加入到隊(duì)列的尾部。

* @return {@code true} if there is a queued thread preceding the
 *         current thread, and {@code false} if the current thread
 *         is at the head of the queue or the queue is empty
 * @since 1.7
 */
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;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

網(wǎng)上有這一張很棒的圖:


公平鎖與非公平鎖

結(jié)合這張圖,二者的區(qū)別更明顯。
公平鎖就是在獲取鎖之前會(huì)先判斷等待隊(duì)列是否為空或者自己是否位于隊(duì)列頭部,該條件通過(guò)才能繼續(xù)獲取鎖。

在結(jié)合兔子喝水的圖分析,非公平鎖獲取所得順序基本決定在9、10、11這三個(gè)事件發(fā)生的先后順序,

  • 1、若在釋放鎖的時(shí)候總是沒(méi)有新的兔子來(lái)打擾,則非公平鎖等于公平鎖;
  • 2、若釋放鎖的時(shí)候,正好一個(gè)兔子來(lái)喝水,而此時(shí)位于隊(duì)列頭的兔子還沒(méi)有被喚醒(因?yàn)榫€程上下文切換是需要不少開(kāi)銷的),此時(shí)后來(lái)的兔子則優(yōu)先獲得鎖,成功打破公平,成為非公平鎖;

其實(shí)對(duì)于非公平鎖,只要線程進(jìn)入了等待隊(duì)列,隊(duì)列里面依然是FIFO的原則,跟公平鎖的順序是一樣的。因?yàn)楣芥i與非公平鎖的release()部分代碼是共用AQS的代碼。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

/**
 * Wakes up node's successor, if one exists.
 */
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}



3、公平鎖與非公平鎖性能對(duì)比

非公平鎖的效率高于公平鎖:上文說(shuō)到的線程切換的開(kāi)銷,其實(shí)就是非公平鎖效率高于公平鎖的原因,因?yàn)榉枪芥i減少了線程掛起的幾率,后來(lái)的線程有一定幾率逃離被掛起的開(kāi)銷。



滬漂程序員一枚。
堅(jiān)持寫博客,如果覺(jué)得還可以的話,給個(gè)小星星哦,你的支持就是我創(chuàng)作的動(dòng)力。


推薦閱讀:
Java內(nèi)存模型-volatile的應(yīng)用(實(shí)例講解)
synchronized解決原子性-synchronized的三種應(yīng)用方式(實(shí)例講解)
線程池-一文弄懂Java里面的線程池ThreadPoolExecutor
可重入鎖-面試題:synchronized是可重入鎖嗎

本文參考:一張圖讀懂非公平鎖與公平鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容