ReentrantLock

簡單介紹

ReentrantLock 是一個可重入的獨占鎖

  • 可重入
    同一線程外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然可以獲取該鎖的代碼
    該特性帶來的兩個問題:
    • 如何識別獲取鎖的線程是否為當前占據(jù)鎖的線程
    • 線程重復(fù) n 次獲取了鎖,需要釋放 n 次鎖,否則會導致別的線程無法獲得鎖
  • 獨占
    一次只能被一個線程所持有
類型
private final Sync sync;

ReentrantLock 的內(nèi)部類 Sync 繼承了 AQSAbstractQueuedSynchronizer),并且有公平鎖 FairSync 和 非公平鎖 NonfaireSync 兩個字類,ReentrantLock 的獲取與釋放鎖操作都是委托給該同步組件來實現(xiàn)的
(注:該篇文章里所有 AQS 相關(guān)的內(nèi)容會再寫一篇相關(guān)的文章,這里不詳細介紹)

  • 公平鎖
    是指當鎖可用時,在鎖上等待時間最長的線程將獲取鎖的使用權(quán)(先來先得)
    使用有參構(gòu)造方法,傳入 true 創(chuàng)建公平鎖
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
  • 非公平鎖
    隨機分配使用權(quán),使用 ReentrantLock 無參的構(gòu)造函數(shù),默認創(chuàng)建的是非公平鎖
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
常用方法介紹
  • lock() 方法獲取鎖,如果獲取不到鎖,則當前線程在獲取到鎖之前都不可調(diào)度(不響應(yīng)中斷)
    public void lock() {
        sync.lock();
    }
    
  • lockInterruptibly() 方法獲取鎖,則當前線程在獲取到鎖之前都不可調(diào)度,除非有其他線程中斷了當前線程(響應(yīng)中斷)
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
  • tryLock() 方法獲取鎖,如果調(diào)用的時候能夠獲取鎖,那么就獲取鎖并且返回 true,如果當前的鎖無法獲取到,那么這個方法會立刻返回 false
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    
  • tryLock(long timeout, TimeUnit unit) 方法獲取鎖,在指定時間內(nèi)嘗試獲取鎖。如果可以獲取鎖,則獲取鎖并返回 true ,如果無法獲取鎖,則當前線程變?yōu)椴豢烧{(diào)度,除非當前線程被中斷或者到了指定的等待時間
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    
  • unlock() 釋放鎖,釋放當前線程占用的鎖。注意:獲取了幾次鎖,就要釋放幾次鎖
    public void unlock() {
        sync.release(1);
    }
    
  • newCondition() 方法,返回一個與當前的鎖關(guān)聯(lián)的條件變量。在使用這個條件變量之前,當前線程必須占用鎖。調(diào)用 Conditionawait 方法,會在等待之前原子地釋放鎖,并在等待被喚醒后原子的獲取鎖
    public Condition newCondition() {
        return sync.newCondition();
    }
    
  • isHeldByCurrentThread() 方法,查詢當前線程是否保持鎖定
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    
  • isLocked() 方法,查詢該鎖是否已經(jīng)被鎖定
    public boolean isLocked() {
        return sync.isLocked();
    }
    
  • isFair() 方法,判斷鎖是公平鎖還是非公平鎖
    public final boolean isFair() {
        return sync instanceof FairSync;
    }
    
Sync 內(nèi)部類
abstract static class Sync extends AbstractQueuedSynchronizer {
  ...
}

Sync 是一個抽象類型,它繼承 AbstractQueuedSynchronizer,這個 AbstractQueuedSynchronizer 是一個模板類,它實現(xiàn)了許多和鎖相關(guān)的功能,并提供了鉤子方法供用戶實現(xiàn),比如 tryAcquiretryRelease

static final class NonfairSync extends Sync {
    final void lock() {
        ...
    }
}
static final class FairSync extends Sync {
    final void lock() {
        ...
    }
}

NonfairSyncFairSync 兩個類繼承自 Sync ,實現(xiàn)了 lock 方法

  • lock
    當我們調(diào)用 ReentrantLocklock 方法的時候,實際上是調(diào)用了 NonfairSync 或者 FairSynclock 方法
    • NonfairSync 非公平鎖的 lock 實現(xiàn)
      final void lock() {
          if (compareAndSetState(0, 1))
              setExclusiveOwnerThread(Thread.currentThread());
          else
              acquire(1);
      }
      
    這個方法先用 CAS 操作,去嘗試搶占該鎖。如果成功,就把當前線程設(shè)置在這個鎖上,表示搶占成功。如果失敗,則調(diào)用 acquire 模板方法,等待搶占
    acquire 方法里調(diào)用了 tryAcquire(int arg) 方法,NonfairSynctryAcquire 實際上又調(diào)用的 SyncnonfairTryAcquire 方法,如下
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) { // 判斷鎖的狀態(tài)是不是 0,如果是,則嘗試去原子搶占這個鎖
            if (compareAndSetState(0, acquires)) { // 如果搶占到了,把狀態(tài)設(shè)置為1
                setExclusiveOwnerThread(current); // 并且設(shè)置當前線程為獨占線程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {// 如果鎖的狀態(tài)不為 0,判斷該線程是否是獨占線程(可重入)
            int nextc = c + acquires; // 如果當前線程是獨占線程,則增加狀態(tài)變量的值
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc); // 給狀態(tài)變量賦值
            return true;
        }
        return false;
    }
    
    tryAcquire 一旦返回 false,就會則進入 acquireQueued 流程,此段代碼中鎖的獲取可以分為兩種情況:
    1. state為0時:代表鎖已經(jīng)釋放,可以去獲取,所以使用 CAS 去獲取鎖,如果獲取成功,則代表競爭鎖成功,調(diào)用 setExclusiveOwnerThread 設(shè)置當前線程為獨占線程,因為隊列中的線程和新線程都可以 CAS 獲取鎖不需要排隊,所以是非公平鎖
    2. status不為0時:代表鎖已經(jīng)被占有,如果當前線程是占有鎖的線程(current == getExclusiveOwnerThread()true),更新state,意味著當前線程又一次的獲取了鎖,這就是可重入。
    • FairSync 公平鎖的 lock 實現(xiàn)
      final void lock() {
          acquire(1);
      }
      
    acquire 方法里同樣調(diào)用了 tryAcquire(int arg) 方法, FairSynctryAcquire 方法實現(xiàn)如下:
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {  // 判斷鎖的狀態(tài)是不是 0,如果是 0,再判斷是否有線程在排隊獲取鎖
            if (!hasQueuedPredecessors() &&  // 如果沒有線程在排隊獲取鎖則嘗試原子搶占鎖
                compareAndSetState(0, acquires)) {  // 如果搶占到了,把狀態(tài)設(shè)置為1
                setExclusiveOwnerThread(current);  // 并且設(shè)置當前線程為獨占線程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 如果鎖的狀態(tài)不為 0,判斷該線程是否是獨占線程(可重入)
            int nextc = c + acquires; // 如果當前線程是獨占線程,則增加狀態(tài)變量的值
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc); // 給狀態(tài)變量賦值
            return true;
        }
        return false;
    }
    
    tryAcquire 一旦返回 false,就會則進入 acquireQueued 流程,公平鎖獲取鎖的過程與非公平鎖不一樣的地方在 state為0時 新線程需要判斷有沒有線程在排隊獲取鎖,只有當沒有的時候才會去嘗試搶占鎖,如果有線程在排隊,新線程也會被加入到排隊的隊列中去
  • unlock
    unlock 方法,其實是直接調(diào)用 AbstractQueuedSynchronizerrelease 操作, release 方法先調(diào)用了 tryRelease 方法,SynctryRelease 方法實現(xiàn)如下:
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases; //狀態(tài)變量值減少,這里是考慮到可重入鎖可能自身會多次占用鎖
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果當前線程不是獨占線程則拋異常
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { // 當狀態(tài)值為 0 ,鎖釋放
            free = true;
            setExclusiveOwnerThread(null); // 將獨占線程設(shè)置為 null
        }
        setState(c); // 狀態(tài)變量賦值
        return free;
    }
    
    一旦 tryRelease 成功,下一個節(jié)點的線程被喚醒,被喚醒的線程就會進入 acquireQueued 流程中,去獲取鎖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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