JUC源碼分析-JUC鎖(一):ReentrantLock

1. 概述

ReentrantLock是一個(gè)可重入的互斥鎖惨奕,也被稱為“獨(dú)占鎖”识颊。在上一篇講解AQS的時(shí)候已經(jīng)提到允耿,“獨(dú)占鎖”在同一個(gè)時(shí)間點(diǎn)只能被一個(gè)線程持有奕删;而可重入的意思是,ReentrantLock可以被單個(gè)線程多次獲取疗认。
ReentrantLock又分為“公平鎖(fair lock)”和“非公平鎖(non-fair lock)”完残。它們的區(qū)別體現(xiàn)在獲取鎖的機(jī)制上:在“公平鎖”的機(jī)制下,線程依次排隊(duì)獲取鎖横漏;而“非公平鎖”機(jī)制下谨设,如果鎖是可獲取狀態(tài),不管自己是不是在隊(duì)列的head節(jié)點(diǎn)都會(huì)去嘗試獲取鎖缎浇。

2. 數(shù)據(jù)結(jié)構(gòu)和核心參數(shù)

ReetrantLock繼承關(guān)系

可以看到ReetrantLock繼承自AQS扎拣,并實(shí)現(xiàn)了Lock接口。Lock源碼如下:

public interface Lock {
    //獲取鎖素跺,如果鎖不可用則線程一直等待
    void lock();
    //獲取鎖二蓝,響應(yīng)中斷,如果鎖不可用則線程一直等待
    void lockInterruptibly() throws InterruptedException;
    //獲取鎖指厌,獲取失敗直接返回
    boolean tryLock();
    //獲取鎖刊愚,等待給定時(shí)間后如果獲取失敗直接返回
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    //釋放鎖
    void unlock();
    //創(chuàng)建一個(gè)新的等待條件
    Condition newCondition();
}

Lock提供的獲取鎖方法中,有lock()踩验、lockInterruptibly()鸥诽、tryLock()tryLock(long time, TimeUnit unit)四種方式商玫,他們的區(qū)別如下:

  • lock() 獲取失敗后,線程進(jìn)入等待隊(duì)列自旋或休眠牡借,直到鎖可用拳昌,并且忽略中斷的影響
  • lockInterruptibly() 線程進(jìn)入等待隊(duì)列park后,如果線程被中斷钠龙,則直接響應(yīng)中斷(拋出InterruptedException
  • tryLock() 獲取鎖失敗后直接返回炬藤,不進(jìn)入等待隊(duì)列
  • tryLock(long time, TimeUnit unit) 獲取鎖失敗等待給定的時(shí)間后返回獲取結(jié)果

ReetrantLock通過(guò)AQS實(shí)現(xiàn)了自己的同步器Sync,分為公平鎖FairSync和非公平鎖NonfairSync俊鱼。在構(gòu)造時(shí)刻像,通過(guò)所傳參數(shù)boolean fair來(lái)確定使用那種類型的鎖。

本篇會(huì)以對(duì)比的方式分析兩種鎖的源碼實(shí)現(xiàn)方式并闲。

3. 源碼解析

3.1 lock()

lock()方法用于獲取鎖细睡,兩種類型的鎖源碼實(shí)現(xiàn)如下:

//獲取鎖,一直等待鎖可用
public void lock() {
    sync.lock();
}

//公平鎖獲取
final void lock() {
    acquire(1);
}

//非公平鎖獲取
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

說(shuō)明:公平鎖的lock方法調(diào)用了AQS的acquire(1)帝火;而非公平鎖則直接通過(guò)CAS修改state值來(lái)獲取鎖溜徙,當(dāng)獲取失敗時(shí)才會(huì)調(diào)用acquire(1)來(lái)獲取鎖。
關(guān)于acquire()方法犀填,在上篇介紹AQS的時(shí)候已經(jīng)講過(guò)蠢壹,印象不深的同學(xué)可以翻回去看一下,這里主要來(lái)看一下tryAcquire在ReetrantLock中的實(shí)現(xiàn)九巡。

公平鎖tryAcquire:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();//獲取鎖狀態(tài)state
    if (c == 0) {
        if (!hasQueuedPredecessors() && //判斷當(dāng)前線程是否還有前節(jié)點(diǎn)
            compareAndSetState(0, acquires)) {//CAS修改state
            //獲取鎖成功图贸,設(shè)置鎖的持有線程為當(dāng)前線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {//當(dāng)前線程已經(jīng)持有鎖
        int nextc = c + acquires;//重入
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);//更新state狀態(tài)
        return true;
    }
    return false;
}

說(shuō)明:公平鎖模式下的tryAcquire,執(zhí)行流程如下:

  1. 如果當(dāng)前鎖狀態(tài)state為0冕广,說(shuō)明鎖處于閑置狀態(tài)可以被獲取疏日,首先調(diào)用hasQueuedPredecessors方法判斷當(dāng)前線程是否還有前節(jié)點(diǎn)(prev node)在等待獲取鎖。如果有撒汉,則直接返回false沟优;如果沒(méi)有,通過(guò)調(diào)用compareAndSetState(CAS)修改state值來(lái)標(biāo)記自己已經(jīng)拿到鎖睬辐,CAS執(zhí)行成功后調(diào)用setExclusiveOwnerThread設(shè)置鎖的持有者為當(dāng)前線程挠阁。程序執(zhí)行到現(xiàn)在說(shuō)明鎖獲取成功,返回true溯饵;
  2. 如果當(dāng)前鎖狀態(tài)state不為0侵俗,但當(dāng)前線程已經(jīng)持有鎖(current == getExclusiveOwnerThread()),由于鎖是可重入(多次獲确峥)的坡慌,則更新重入后的鎖狀態(tài)state += acquires 。鎖獲取成功返回true藻三。

非公平鎖tryAcquire

//非公平鎖獲取
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {//CAS修改state
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;//計(jì)算重入后的state
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

說(shuō)明:通過(guò)對(duì)比公平鎖和非公平鎖tryAcquire的代碼可以看到洪橘,非公平鎖的獲取略去了!hasQueuedPredecessors()這一操作跪者,也就是說(shuō)它不會(huì)判斷當(dāng)前線程是否還有前節(jié)點(diǎn)(prev node)在等待獲取鎖,而是直接去進(jìn)行鎖獲取操作熄求。

3.2 unlock()

//釋放鎖
public void unlock() {
    sync.release(1);
}

說(shuō)明:關(guān)于release()方法渣玲,在上篇介紹AQS的時(shí)候已經(jīng)講過(guò),印象不深的同學(xué)可以翻回去看一下弟晚,這里主要來(lái)看一下tryRelease在ReetrantLock中的實(shí)現(xiàn):

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//計(jì)算釋放后的state值
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;//鎖全部釋放忘衍,可以喚醒下一個(gè)等待線程
        setExclusiveOwnerThread(null);//設(shè)置鎖持有線程為null
    }
    setState(c);
    return free;
}

說(shuō)明:tryRelease用于釋放給定量的資源。在ReetrantLock中每次釋放量為1卿城,也就是說(shuō)瑟押,在可重入鎖中,獲取鎖的次數(shù)必須要等于釋放鎖的次數(shù),這樣才算是真正釋放了鎖饭于。在鎖全部釋放后(state==0)才可以喚醒下一個(gè)等待線程木西。

3.3 等待條件Condition

在上篇介紹AQS中提到過(guò)八千,在AQS中不光有等待隊(duì)列重绷,還有一個(gè)條件隊(duì)列愤钾,這個(gè)條件隊(duì)列就是我們接下來(lái)要講的Condition杂瘸。
Condition的作用是對(duì)鎖進(jìn)行更精確的控制镜硕。Condition中的await()血淌、signal()峰伙、signalAll()方法相當(dāng)于Object的wait()、notify()、notifyAll()方法。不同的是,Object中的wait()、notify()、notifyAll()方法是和"同步鎖"(synchronized關(guān)鍵字)捆綁使用的看幼;而Condition是需要與Lock捆綁使用的苞轿。

Condition函數(shù)列表

//使當(dāng)前線程在被喚醒或被中斷之前一直處于等待狀態(tài)契邀。
void await()

//使當(dāng)前線程在被喚醒、被中斷或到達(dá)指定等待時(shí)間之前一直處于等待狀態(tài)古戴。
boolean await(long time, TimeUnit unit)

//使當(dāng)前線程在被喚醒欠橘、被中斷或到達(dá)指定等待時(shí)間之前一直處于等待狀態(tài)。
long awaitNanos(long nanosTimeout)

//使當(dāng)前線程在被喚醒之前一直處于等待狀態(tài)现恼。
void awaitUninterruptibly()

//使當(dāng)前線程在被喚醒肃续、被中斷或到達(dá)指定最后期限之前一直處于等待狀態(tài)。
boolean awaitUntil(Date deadline)

//喚醒一個(gè)等待線程叉袍。
void signal()

//喚醒所有等待線程始锚。
void signalAll()

下面我們來(lái)看一下Condition在AQS中的實(shí)現(xiàn)

3.3.1 await()

//使當(dāng)前線程在被喚醒或被中斷之前一直處于等待狀態(tài)。
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();//添加并返回一個(gè)新的條件節(jié)點(diǎn)
    int savedState = fullyRelease(node);//釋放全部資源
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        //當(dāng)前線程不在等待隊(duì)列喳逛,park阻塞
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            //線程被中斷瞧捌,跳出循環(huán)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();//解除條件隊(duì)列中已經(jīng)取消的等待節(jié)點(diǎn)的鏈接
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);//等待結(jié)束后處理中斷
}

說(shuō)明: await()方法相當(dāng)于Object的wait()。把當(dāng)前線程添加到條件隊(duì)列中調(diào)用LockSupport.park()阻塞润文,直到被喚醒或中斷姐呐。函數(shù)流程如下:

  1. 首先判斷線程是否被中斷,如果是转唉,直接拋出InterruptedException皮钠,否則進(jìn)入下一步稳捆;
  2. 添加當(dāng)前線程到條件隊(duì)列中赠法,然后釋放全部資源/鎖;
  3. 如果當(dāng)前節(jié)點(diǎn)不在等待隊(duì)列中,調(diào)用LockSupport.park()阻塞當(dāng)前線程,直到被unpark或被中斷砖织。這里先簡(jiǎn)單說(shuō)一下signal方法款侵,在線程接收到signal信號(hào)后,unpark當(dāng)前線程侧纯,并把當(dāng)前線程轉(zhuǎn)移到等待隊(duì)列中(sync queue)新锈。所以,在當(dāng)前方法中眶熬,如果線程被解除阻塞(unpark)妹笆,也就是說(shuō)當(dāng)前線程被轉(zhuǎn)移到等待隊(duì)列中,就會(huì)跳出while循環(huán)娜氏,進(jìn)入下一步拳缠;
  4. 線程進(jìn)入等待隊(duì)列后,調(diào)用acquireQueued方法獲取鎖贸弥;
  5. 調(diào)用unlinkCancelledWaiters方法檢查條件隊(duì)列中已經(jīng)取消的節(jié)點(diǎn)窟坐,并解除它們的鏈接(這些取消的節(jié)點(diǎn)在隨后的垃圾收集中被回收掉);
  6. 邏輯處理結(jié)束绵疲,最后處理中斷(拋出InterruptedException或把忽略的中斷補(bǔ)上)哲鸳。

3.3.2 signal()

//喚醒線程
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);//喚醒條件隊(duì)列的首節(jié)點(diǎn)線程
}

//從條件隊(duì)列中移除給定節(jié)點(diǎn),并把它轉(zhuǎn)移到等待隊(duì)列
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null; //解除首節(jié)點(diǎn)鏈接
    } while (!transferForSignal(first) && //接收到signal信號(hào)后盔憨,把節(jié)點(diǎn)轉(zhuǎn)入等待隊(duì)列
             (first = firstWaiter) != null);
}

//接收到signal信號(hào)后徙菠,把節(jié)點(diǎn)轉(zhuǎn)入等待隊(duì)列
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        //CAS修改狀態(tài)失敗,說(shuō)明節(jié)點(diǎn)被取消郁岩,直接返回false
        return false;

    Node p = enq(node);//添加節(jié)點(diǎn)到等待隊(duì)列懒豹,并返回節(jié)點(diǎn)的前繼節(jié)點(diǎn)(prev)
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //如果前節(jié)點(diǎn)被取消,說(shuō)明當(dāng)前為最后一個(gè)等待線程驯用,unpark喚醒當(dāng)前線程
        LockSupport.unpark(node.thread);
    return true;
}

說(shuō)明:signal方法用于發(fā)送喚醒信號(hào)脸秽。在不考慮線程爭(zhēng)用的情況下,執(zhí)行流程如下:

  1. 獲取條件隊(duì)列的首節(jié)點(diǎn)蝴乔,解除首節(jié)點(diǎn)的鏈接(first.nextWaiter = null;)记餐;
  2. 調(diào)用transferForSignal把條件隊(duì)列的首節(jié)點(diǎn)轉(zhuǎn)移到等待隊(duì)列的尾部。在transferForSignal中薇正,轉(zhuǎn)移節(jié)點(diǎn)后片酝,轉(zhuǎn)移的節(jié)點(diǎn)沒(méi)有前繼節(jié)點(diǎn),說(shuō)明當(dāng)前最后一個(gè)等待線程挖腰,直接調(diào)用unpark()喚醒當(dāng)前線程雕沿。

Condition的其他例如awaitNanos(long nanosTimeout)、signalAll()等方法這里這里就不多贅述了猴仑,執(zhí)行流程都差不多审轮,同學(xué)們可以參考上述分析閱讀。

synchronized和ReentrantLock的選擇

ReentrantLock在加鎖和內(nèi)存上提供的語(yǔ)義與內(nèi)置鎖synchronized相同,此外它還提供了一些其他功能疾渣,包括定時(shí)的鎖等待篡诽、可中斷的鎖等待、公平性榴捡,以及實(shí)現(xiàn)非塊結(jié)構(gòu)的加鎖杈女。從性能方面來(lái)說(shuō),在JDK5的早期版本中吊圾,ReentrantLock的性能遠(yuǎn)遠(yuǎn)好于synchronized达椰,但是從JDK6開(kāi)始,JDK在synchronized上做了大量?jī)?yōu)化项乒,使得兩者的性能差距不大砰碴。synchronized的優(yōu)點(diǎn)就是簡(jiǎn)潔。 所以說(shuō)板丽,兩者之間的選擇還是要看具體的需求呈枉,ReentrantLock可以作為一種高級(jí)工具,當(dāng)需要一些高級(jí)功能時(shí)可以使用它埃碱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猖辫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子砚殿,更是在濱河造成了極大的恐慌啃憎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件似炎,死亡現(xiàn)場(chǎng)離奇詭異辛萍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)羡藐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門贩毕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人仆嗦,你說(shuō)我怎么就攤上這事辉阶。” “怎么了瘩扼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谆甜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我集绰,道長(zhǎng)规辱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任栽燕,我火速辦了婚禮罕袋,結(jié)果婚禮上改淑,老公的妹妹穿的比我還像新娘。我一直安慰自己炫贤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布付秕。 她就那樣靜靜地躺著兰珍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪询吴。 梳的紋絲不亂的頭發(fā)上掠河,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音猛计,去河邊找鬼唠摹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奉瘤,可吹牛的內(nèi)容都是我干的勾拉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼盗温,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼藕赞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起卖局,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤斧蜕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后砚偶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體批销,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年染坯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了均芽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡单鹿,死狀恐怖骡技,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羞反,我是刑警寧澤布朦,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站昼窗,受9級(jí)特大地震影響是趴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澄惊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一唆途、第九天 我趴在偏房一處隱蔽的房頂上張望富雅。 院中可真熱鬧,春花似錦肛搬、人聲如沸没佑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛤奢。三九已至,卻和暖如春陶贼,著一層夾襖步出監(jiān)牢的瞬間啤贩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拜秧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痹屹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓枉氮,卻偏偏與公主長(zhǎng)得像志衍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子聊替,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 上一篇文章《基于CAS操作的Java非阻塞同步機(jī)制》 分析了非同步阻塞機(jī)制的實(shí)現(xiàn)原理足画,本篇將分析一種以非同步...
    Mars_M閱讀 4,803評(píng)論 5 9
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-03】 更新日志 前言 在java中,鎖是實(shí)現(xiàn)并發(fā)的關(guān)鍵組件佃牛,多個(gè)...
    一字馬胡閱讀 44,151評(píng)論 1 32
  • AQS 隊(duì)列同步器(AbstractQueuedSynchronizer)簡(jiǎn)稱AQS,是J.U.C同步構(gòu)件的基礎(chǔ)淹辞,...
    wangjie2016閱讀 2,146評(píng)論 1 10
  • 一、多線程 說(shuō)明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)俘侠。 NEW:這種情況指的是象缀,通過(guò) New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,679評(píng)論 0 44
  • 晚上讀書(shū)的時(shí)候,萱萱自己拿了一個(gè)沙琪瑪爷速,一袋酸奶央星,一個(gè)香蕉,放在盤子里端過(guò)來(lái)說(shuō):“媽媽惫东,讀書(shū)吧莉给!”結(jié)果讀的時(shí)候光顧...
    Jenny2011閱讀 175評(píng)論 0 0