6 ReentrantLock

ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機制。它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更強大、靈活的鎖機制,可以減少死鎖發(fā)生的概率。
API介紹如下:

一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監(jiān)視器鎖定相同的一些基本行為和語義,但功能更強大。ReentrantLock 將由最近成功獲得鎖定,并且還沒有釋放該鎖定的線程所擁有。當(dāng)鎖定沒有被另一個線程所擁有時,調(diào)用 lock 的線程將成功獲取該鎖定并返回。如果當(dāng)前線程已經(jīng)擁有該鎖定,此方法將立即返回??梢允褂?isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發(fā)生。

ReentrantLock還提供了公平鎖非公平鎖的選擇,構(gòu)造方法接受一個可選的公平參數(shù)(默認非公平鎖),當(dāng)設(shè)置為true時,表示公平鎖,否則為非公平鎖。
公平鎖與非公平鎖的區(qū)別在于公平鎖的鎖獲取是有順序的。但是公平鎖的效率往往沒有非公平鎖的效率高,在許多線程訪問的情況下,公平鎖表現(xiàn)出較低的吞吐量。

獲取鎖

我們一般都是這么使用ReentrantLock獲取鎖的:

//非公平鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();

然后看看lock()

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

Sync為ReentrantLock里面的一個內(nèi)部類,它繼承AQS(AbstractQueuedSynchronizer),它有兩個子類:公平鎖FairSync和非公平鎖NonfairSync。

ReentrantLock里面大部分的功能都是委托給Sync來實現(xiàn)的,同時Sync內(nèi)部定義了lock()抽象方法由其子類去實現(xiàn),默認實現(xiàn)了nonfairTryAcquire(int acquires)方法,可以看出它是非公平鎖的默認實現(xiàn)方式。下面我們看非公平鎖的lock()方法:

final void lock() {
    //嘗試獲取鎖
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //獲取失敗,調(diào)用AQS的acquire(int arg)方法
        acquire(1);
}

首先會第一次嘗試快速獲取鎖,如果獲取失敗,則調(diào)用acquire(int arg)方法,該方法定義在AQS中,如下:

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

這個方法首先調(diào)用tryAcquire(int arg)方法,在AQS中講述過,tryAcquire(int arg)需要自定義同步組件提供實現(xiàn),非公平鎖實現(xiàn)如下:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    //當(dāng)前線程
    final Thread current = Thread.currentThread();
    //獲取同步狀態(tài)
    int c = getState();
    //state == 0,表示沒有該鎖處于空閑狀態(tài)
    if (c == 0) {
        //獲取鎖成功,設(shè)置為當(dāng)前線程所有
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //線程重入
    //判斷鎖持有的線程是否為當(dāng)前線程
    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;
}

該方法主要邏輯:首先判斷同步狀態(tài)state == 0 ?,如果是表示該鎖還沒有被線程持有,直接通過CAS獲取同步狀態(tài),如果成功返回true。如果state != 0,則判斷當(dāng)前線程是否為獲取鎖的線程,如果是則獲取鎖,成功返回true。成功獲取鎖的線程再次獲取鎖,這是增加了同步狀態(tài)state。

釋放鎖

獲取同步鎖后,使用完畢則需要釋放鎖,ReentrantLock提供了unlock釋放鎖:

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

unlock內(nèi)部使用Sync的release(int arg)釋放鎖,release(int arg)是在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;
}

與獲取同步狀態(tài)的acquire(int arg)方法相似,釋放同步狀態(tài)的tryRelease(int arg)同樣是需要自定義同步組件自己實現(xiàn):

protected final boolean tryRelease(int releases) {
    //減掉releases
    int c = getState() - releases;
    //如果釋放的不是持有鎖的線程,拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //state == 0 表示已經(jīng)釋放完全了,其他線程可以獲取同步狀態(tài)了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

只有當(dāng)同步狀態(tài)徹底釋放后該方法才會返回true。當(dāng)state == 0 時,則將鎖持有線程設(shè)置為null,free= true,表示釋放成功。

公平鎖與非公平鎖

公平鎖與非公平鎖的區(qū)別在于獲取鎖的時候是否按照FIFO的順序來。釋放鎖不存在公平性和非公平性,上面以非公平鎖為例,下面我們來看看公平鎖的tryAcquire(int arg):

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

比較非公平鎖和公平鎖獲取同步狀態(tài)的過程,會發(fā)現(xiàn)兩者唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時多了一個限制條件:hasQueuedPredecessors(),定義如下:

public final boolean hasQueuedPredecessors() {
    Node t = tail;  //尾節(jié)點
    Node h = head;  //頭節(jié)點
    Node s;
    
    //頭節(jié)點 != 尾節(jié)點
    //同步隊列第一個節(jié)點不為null
    //當(dāng)前線程是同步隊列第一個節(jié)點
    return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

該方法主要做一件事情:主要是判斷當(dāng)前線程是否位于CLH同步隊列中的第一個。如果是則返回true,否則返回false。

公平鎖和非公平鎖的區(qū)別

  1. 公平鎖每次獲取到鎖為同步隊列中的第一個節(jié)點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續(xù)獲取該鎖,則有可能導(dǎo)致其他線程永遠無法獲取到鎖,造成“饑餓”現(xiàn)象。

  2. 公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,ReentrantLock默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。

ReentrantLock與synchronized的區(qū)別

ReentrantLock是Lock的實現(xiàn)類,是一個互斥的同步器,在多線程高競爭條件下,ReentrantLock比synchronized有更加優(yōu)異的性能表現(xiàn)。

  1. 用法比較
  • Lock使用起來比較靈活,但是必須有釋放鎖的配合動作
  • Lock必須手動獲取與釋放鎖,而synchronized不需要手動釋放和開啟鎖
  • Lock只適用于代碼塊鎖,而synchronized可用于修飾方法、代碼塊等
  1. 特性比較
    ReentrantLock的優(yōu)勢體現(xiàn)在:
    具備嘗試非阻塞地獲取鎖的特性:當(dāng)前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖
    能被中斷地獲取鎖的特性:與synchronized不同,獲取到鎖的線程能夠響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放
    超時獲取鎖的特性:在指定的時間范圍內(nèi)獲取鎖;如果截止時間到了仍然無法獲取鎖,則返回

3 注意事項
在使用ReentrantLock類的時,一定要注意三點:
在finally中釋放鎖,目的是保證在獲取鎖之后,最終能夠被釋放
不要將獲取鎖的過程寫在try塊內(nèi),因為如果在獲取鎖時發(fā)生了異常,異常拋出的同時,也會導(dǎo)致鎖無故被釋放。
ReentrantLock提供了一個newCondition的方法,以便用戶在同一鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒的動作。

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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