ReentrantLock可重入鎖—源碼詳解


開始這篇博客之前,博主默認大家都是看過AQS源碼的~什么居然沒看過🤬猛戳下方👇👇👇
全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(一)AQS基礎
全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(二)資源的獲取和釋放
全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(三)條件變量

介紹

ReentrantLock可重入鎖,是JUC提供的一種最常用的鎖。“可重入”的意思就是:同一個線程可以無條件地反復獲得已經持有的鎖

ReentrantLock公平鎖非公平鎖兩種模式,底層使用的正是AbstractQueuedSynchronizer這個偉大的並發工具

ReentrantLock的結構如下圖所示:

ReentrantLock實現了Lock接口,該接口定義了一個鎖應該具備的基本功能,即加鎖、解鎖、創建條件變量等功能。源碼如下:

public interface Lock {
    
    void lock();
    
    void lockInterruptibly() throws InterruptedException;
    
    boolean tryLock();
    
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    void unlock();
    
    Condition newCondition();
}

使用ReentrantLock,主要使用lockunlock方法,也會用到newCondition來創建條件變量,實現一些條件同步功能

回到上面的結構圖,可以看到,ReentrantLock的功能主要是借助其內部類Sync來實現,而Sync類是繼承了AbstractQueuedSynchronizer,並衍生出兩個子類FairSyncNonfairSync,分別對應公平鎖和非公平鎖兩種模式。實際應用中,一般非公平鎖的效率要高於公平鎖。具體原因見最后一節“相關面試題"

作者: 酒冽        出處: https://www.cnblogs.com/frankiedyz/p/15719681.html
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則 必究法律責任

Sync

ReentrantLock中的sync域是是一個Sync類對象,ReentrantLock使用sync來實現主要的功能。Sync類是ReentrantLock的內部類:

/** Synchronizer providing all implementation mechanics */
private final Sync sync;

Sync類繼承了AQS,使用AQS的state作為鎖的重入數,其源碼如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    // 用於輔助ReentrantLock執行Lock接口的lock方法,所以定義了一個同名lock方法
    abstract void lock();

    // 執行非公平的tryLock,是非公平鎖子類NonFairSync實現的tryAcquire方法的主要邏輯,也是ReentrantLock的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;
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    // 為了支持使用條件變量,Sync實現了AQS中的isHeldExclusively,並提供了newCondition方法創建條件變量
    
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }
    
    // ReentrantLock的三個同名方法,都委托給了下面這三個方法,用於獲取鎖的一些信息
    
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }

    /**
    * Reconstitutes the instance from a stream (that is, deserializes it).
    */
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

總結一下,Sync類有以下幾個作用:

  • 定義抽象lock方法:Sync定義了lock這一個抽象方法,強迫兩個子類去實現,而ReentrantLocklock方法就可以直接委托Synclock方法去執行
  • 非公平模式的嘗試獲取鎖:Sync提供了nonfairTryAcquire方法,提供非公平嘗試獲取鎖的方法。不僅非公平子類NonfairSync實現tryAcquire方法需要委托給nonfairTryAcquire來處理,而且ReentrantLock中的tryLock方法也會委托給它來處理
  • 嘗試釋放鎖:Sync實現了AQS中的tryRelease方法,因為不管是公平模式還是非公平模式,釋放鎖的邏輯都是相同的,因此在Sync這一層就提供了具體的實現,而沒有下放給子類來實現
  • 條件變量的支持:Sync實現了AQS中的isHeldExclusively方法(該方法會被AQS中的ConditionObjectsignal方法調用),並提供了newCondition方法創建條件變量
  • 獲取鎖信息的方法:Sync提供了getOwnergetHoldCountisLocked三個方法用於獲取鎖的信息,外圍類ReentrantLock的三個同名方法會委托這三個方法來執行

獲取鎖

要利用AQS實現獲取鎖的功能,需要實現tryAcquire方法。但是由於公平模式和非公平模式下獲取鎖的邏輯不同,因此tryAcquire交給兩個子類去實現,Sync並不實現

但是對於非公平模式的獲取鎖,NonFairSync子類實現的tryAcquire方法實際上委托了Sync類的nonfairTryAcquire方法來處理。nonfairTryAcquire的源碼分析放在后面的非公平模式去講解

釋放鎖

要利用AQS實現釋放鎖的功能,需要實現tryRelease方法。不同於獲取鎖,對於公平模式和非公平模式來說,釋放鎖的邏輯是相同的,因此tryRelease的實現直接交給Sync這一層來實現,而沒有下放給子類來實現

tryRelease是嘗試釋放資源,而在ReentrantLock中的語義環境下就是嘗試釋放鎖。其源碼如下:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 如果當前線程不持有鎖就去釋放鎖,會拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    
    // 如果釋放鎖后發現鎖空閑
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;		// 返回鎖是否空閑,如果空閑則為true
}

tryReleasereleases參數說明:
由於每次只釋放一個鎖,所以調用unlock釋放鎖時tryReleasereleases參數恆為1
但是ReentrantLock支持條件變量,條件變量的await方法也會調用tryRelease方法一次性釋放所有的鎖資源,此時tryRelease的參數releases不一定為1

AQS中的release方法會調用tryRelease方法並接收其返回值,如下:

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

如果tryRelease返回true,說明鎖為空閑,那么就需要喚醒等待獲取鎖而阻塞,且等待最久的線程,讓它來獲取鎖。因此release會喚醒同步隊列的隊首線程。如果鎖不是空閑,就不需要喚醒任何線程

作者: 酒冽        出處: https://www.cnblogs.com/frankiedyz/p/15719681.html
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則 必究法律責任

非公平鎖

非公平鎖是借助Sync和其子類NonfairSync來實現的。NonfairSync實現了Sync定義的lock抽象方法,以及實現了AQS中的tryAcquire方法以獲取鎖。源碼如下:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    // 實現了Sync中定義的lock方法
    final void lock() {
        // 上來就CAS,一點也不客氣————非公平性
        if (compareAndSetState(0, 1))		// 如果state為0,說明鎖是空閑的,直接CAS獲取
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);	// 如果鎖不空閑,或者CAS競爭失敗,就調用acquire去獲取1個鎖,可能會被阻塞
    }

    // 非公平競爭鎖,實際上委托Sync.nonfairTryAcquire來執行
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

lock方法先直接CAS修改state,如果鎖空閑且修改成功,則說明獲取到了鎖,這里也體現出非公平性,因為它不會謙讓已經在同步隊列中等待的線程
如果鎖非空閑或者競爭失敗,則會調用acquire方法。acquire會調用非公平鎖實現的tryAcquire方法,再次進行競爭,可能直接獲取到鎖,也可能再次失敗,進入同步隊列阻塞等待,這里同樣體現了非公平性

非公平鎖實現的tryAcquire實際委托Sync.nonfairTryAcquire方法來執行,該方法源碼如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果鎖空閑,那么直接CAS修改————非公平性
    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);		// 直接設置不用CAS
        return true;
    }
    return false;				// 獲取失敗
}

第一個if體現了該方法的非公平性,獲取鎖的線程不會給同步隊列的隊首線程“謙讓”,而是直接上去CAS競爭,如果競爭成功,將比隊首線程更先獲得鎖,這體現了不公平性

ReentrantLock默認創建出來的是非公平鎖,因為非公平鎖的效率一般要高於公平鎖:

public ReentrantLock() {
    sync = new NonfairSync();
}
作者: 酒冽        出處: https://www.cnblogs.com/frankiedyz/p/15719681.html
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則 必究法律責任

公平鎖

公平鎖是借助Sync和其子類FairSync來實現的。FairSync實現了Sync定義的lock抽象方法,以及實現了AQS中的tryAcquire方法以獲取鎖。源碼如下:

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) {
            // 先檢查有無排隊等待的線程,如果有就不去CAS競爭——公平性
            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;
    }
}

lock方法直接調用acquire方法獲取鎖,而acquire會調用非公平鎖實現的tryAcquire方法,而tryAcquire也遵循公平性,因此該lock方法整體上就是公平的

tryAcquire方法會檢查鎖是否空閑,如果空閑,也不會立即去CAS爭奪,而是調用AQS的hasQueuedPredecessors方法檢查是否有線程在同步隊列中等待,如果沒有才會CAS競爭。如果有就說明不能競爭,返回false

AQS中的hasQueuedPredecessors方法會檢查是否有線程在同步隊列中等待,源碼如下:

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    // 如果head等於tail,說明是空隊列
    // 如果隊首的thread域不是當前線程,說明有別的線程先於當前線程等待獲取鎖
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

要使用公平模式的鎖,需要將`ReentrantLock`的構造參數`fair`設為true。如果是false或不設置,則創建的都是非公平模式的鎖:
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
作者: 酒冽        出處: https://www.cnblogs.com/frankiedyz/p/15719681.html
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則 必究法律責任

Lock接口的實現

ReentrantLock實現了Lock接口的所有方法,如下:

public void lock() {
    sync.lock();
}

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public void unlock() {
    sync.release(1);
}

public Condition newCondition() {
    return sync.newCondition();
}

可以看到,ReentrantLock實現的所有Lock方法其實都是委托給了Sync(AQS)來執行

作者: 酒冽        出處: https://www.cnblogs.com/frankiedyz/p/15719681.html
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則 必究法律責任

相關面試題

ReentrantLock是如何實現可重入的

無論是公平鎖還是非公平鎖,獲取鎖調用tryAcquire方法時,獲取成功后都會設置當前持有鎖的線程是自己。如果再次獲取該鎖,當發現鎖已經被持有時,會判斷持有鎖的線程是否是自己,如果是就可以不用競爭而直接獲取鎖

簡述非公平鎖和公平鎖之間的區別

  • 定義角度來說:

    • 公平鎖:獲取鎖的順序和請求鎖的順序是一致的,即誰申請得早(等待得久),誰就最先獲取鎖
    • 非公平鎖:競爭鎖時,等待時間最長的線程和剛剛過來競爭鎖(不在阻塞同步隊列中)的線程都有可能獲取鎖,CPU時間片輪詢到哪個線程,哪個就能獲得鎖
  • 源碼角度來說:
    當鎖被占用時,請求鎖的所有線程都會按照FIFO的順序在同步隊列中阻塞等待。在鎖被釋放的時候,如果是非公平鎖,則隊首線程和剛剛過來請求鎖而不在阻塞隊列中的線程,都可能獲得鎖。如果是公平鎖,就一定是隊首線程獲得鎖,剛剛過來請求鎖得線程會被加入同步隊列阻塞等待

  • 效率上來說:公平鎖效率低於非公平鎖,主要是兩方面的開銷

    • 代碼執行上的開銷:公平模式下會多執行一個方法,該方法用於判斷是否有其他線程正在同步隊列中等待
    • 系統層面的開銷:公平模式下,隊首線程是阻塞的,所以必須先將隊首線程喚醒,這涉及用戶態和內核態的切換,開銷較大。而在非公平模式下,可能是剛剛過來請求鎖的線程獲得鎖,而該線程已經是喚醒狀態,無需喚醒

為什么ReentrantLock.lock方法不能被其他線程中斷

因為lock方法調用的是AQS中的acquire方法,該方法忽略中斷。而acquire方法又會調用acquireQueued方法,該方法執行過程中如果有其他線程中斷了當前線程,只會將中斷記錄下來,不會響應中斷。如果鎖已經被獲取,那么該線程需要被阻塞,阻塞調用的是LockSupport.unpark方法,該方法接收到中斷信號后,不會拋出中斷異常,而是返回。返回之后又會進入acquireQueued的循環,如果不是隊首,就重新被阻塞。所以整個過程都不會被其他線程中斷,只會將中斷記錄下來

ReentrantLocksynchronized之間的相同和不同點

相同點
它們都是通過加鎖實現同步,而且都是阻塞式同步,而不是非阻塞式(自旋鎖),即當一個線程獲取鎖后,其他線程再請求鎖就會失敗而被阻塞,等到鎖釋放才有機會被喚醒

不同點

  • ReentrantLock:是Java 5之后提供的API層面的互斥鎖;需要lockunlock配合tryfinally使用;支持定時獲取鎖功能;支持可中斷的加鎖方法lockInterruptibly,在等待獲取鎖時響應中斷,會拋出中斷異常
  • synchronized:是Java語言的關鍵字,通過JVM實現;使用便捷;不支持定時獲取鎖功能;synchronized在等待獲取鎖時不響應中斷,不拋出中斷異常,只記錄中斷狀態

公平鎖和非公平鎖之間最大的區別在哪里(一句話版本)?

  • 公平鎖:先到臨界區的線程一定會比后到的先獲得鎖
  • 非公平鎖:先到臨界區的線程不一定比后到的先獲得鎖

synchronized加鎖是公平鎖還是非公平鎖?

synchronized非公平鎖

如果使用synchronized鎖,在線程到達臨界區時就直接CAS嘗試獲取鎖,如果失敗則升級為輕量級鎖,再不斷CAS請求鎖。當CAS失敗到達一定次數之后,升級為重量級鎖,放入monitor對象隊列中阻塞等待。而且入隊之前也會先嘗試獲取鎖,獲取不到才進入等待隊列

因此,線程獲取synchronized鎖都不會關心有沒有其他線程之前獲取過,所以synchronized是非公平鎖

為什么要設置前驅節點的狀態為SIGNAL

為了表示前驅節點的后繼節點對應的線程需要被喚醒,就這么簡單


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM