成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

逐行分析AQS源碼(2)——獨(dú)占鎖的釋放

tinna / 2228人閱讀

摘要:我們知道,這個(gè)函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。注意,中斷對(duì)線程來(lái)說只是一個(gè)建議,一個(gè)線程被中斷只是其中斷狀態(tài)被設(shè)為線程可以選擇忽略這個(gè)中斷,中斷一個(gè)線程并不會(huì)影響線程的執(zhí)行。

前言

系列文章目錄

上一篇文章 我們逐行分析了獨(dú)占鎖的獲取操作, 本篇文章我們來(lái)看看獨(dú)占鎖的釋放。如果前面的鎖的獲取流程你已經(jīng)趟過一遍了, 那鎖的釋放部分就很簡(jiǎn)單了, 這篇文章我們直接開始看源碼.

開始之前先提一句, JAVA的內(nèi)置鎖在退出臨界區(qū)之后是會(huì)自動(dòng)釋放鎖的, 但是ReentrantLock這樣的顯式鎖是需要自己顯式的釋放的, 所以在加鎖之后一定不要忘記在finally塊中進(jìn)行顯式的鎖釋放:

Lock lock = new ReentrantLock();
...
lock.lock();
try {
    // 更新對(duì)象
    //捕獲異常
} finally {
    lock.unlock();
}

一定要記得在 finally 塊中釋放鎖! ! !
一定要記得在 finally 塊中釋放鎖! ! !
一定要記得在 finally 塊中釋放鎖! ! !

Example: ReentrantLock的鎖釋放

由于鎖的釋放操作對(duì)于公平鎖和非公平鎖都是一樣的, 所以, unlock的邏輯并沒有放在 FairSyncNonfairSync 里面, 而是直接定義在 ReentrantLock類中:

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

由于釋放鎖的邏輯很簡(jiǎn)單, 這里就不畫流程圖了, 我們直接看源碼:

release

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

可以看出, 相比獲取鎖的acquire方法, 釋放鎖的過程要簡(jiǎn)單很多, 它只涉及到兩個(gè)子函數(shù)的調(diào)用:

tryRelease(arg)

該方法由繼承AQS的子類實(shí)現(xiàn), 為釋放鎖的具體邏輯

unparkSuccessor(h)

喚醒后繼線程

下面我們分別分析這兩個(gè)子函數(shù)

tryRelease

tryRelease方法由ReentrantLock的靜態(tài)類Sync實(shí)現(xiàn):

多嘴提醒一下, 能執(zhí)行到釋放鎖的線程, 一定是已經(jīng)獲取了鎖的線程(這不廢話嘛!)

另外, 相比獲取鎖的操作, 這里并沒有使用任何CAS操作, 也是因?yàn)楫?dāng)前線程已經(jīng)持有了鎖, 所以可以直接安全的操作, 不會(huì)產(chǎn)生競(jìng)爭(zhēng).

protected final boolean tryRelease(int releases) {
    
    // 首先將當(dāng)前持有鎖的線程個(gè)數(shù)減1(回溯到調(diào)用源頭sync.release(1)可知, releases的值為1)
    // 這里的操作主要是針對(duì)可重入鎖的情況下, c可能大于1
    int c = getState() - releases; 
    
    // 釋放鎖的線程當(dāng)前必須是持有鎖的線程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    // 如果c為0了, 說明鎖已經(jīng)完全釋放了
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

是不是很簡(jiǎn)單? 代碼都是自解釋的, LZ就不多嘴了.

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

鎖成功釋放之后, 接下來(lái)就是喚醒后繼節(jié)點(diǎn)了, 這個(gè)方法同樣定義在AQS中.

值得注意的是, 在成功釋放鎖之后(tryRelease 返回 true之后), 喚醒后繼節(jié)點(diǎn)只是一個(gè) "附加操作", 無(wú)論該操作結(jié)果怎樣, 最后 release操作都會(huì)返回 true.

事實(shí)上, unparkSuccessor 函數(shù)也不會(huì)返回任何值

接下來(lái)我們就看看unparkSuccessor的源碼:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    
    // 如果head節(jié)點(diǎn)的ws比0小, 則直接將它設(shè)為0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 通常情況下, 要喚醒的節(jié)點(diǎn)就是自己的后繼節(jié)點(diǎn)
    // 如果后繼節(jié)點(diǎn)存在且也在等待鎖, 那就直接喚醒它
    // 但是有可能存在 后繼節(jié)點(diǎn)取消等待鎖 的情況
    // 此時(shí)從尾節(jié)點(diǎn)開始向前找起, 直到找到距離head節(jié)點(diǎn)最近的ws<=0的節(jié)點(diǎn)
    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; // 注意! 這里找到了之并有return, 而是繼續(xù)向前找
    }
    // 如果找到了還在等待鎖的節(jié)點(diǎn),則喚醒它
    if (s != null)
        LockSupport.unpark(s.thread);
}

在上一篇文章分析 shouldParkAfterFailedAcquire 方法的時(shí)候, 我們重點(diǎn)提到了當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的 waitStatus 屬性, 該屬性決定了我們是否要掛起當(dāng)前線程, 并且我們知道, 如果一個(gè)線程被掛起了, 它的前驅(qū)節(jié)點(diǎn)的 waitStatus值必然是Node.SIGNAL.

在喚醒后繼節(jié)點(diǎn)的操作中, 我們也需要依賴于節(jié)點(diǎn)的waitStatus值.

下面我們仔細(xì)分析 unparkSuccessor函數(shù):

首先, 傳入該函數(shù)的參數(shù)node就是頭節(jié)點(diǎn)head, 并且條件是

h != null && h.waitStatus != 0

h!=null 我們?nèi)菀桌斫? h.waitStatus != 0是個(gè)什么意思呢?

我不妨逆向來(lái)思考一下, waitStatus在什么條件下等于0? 從上一篇文章到現(xiàn)在, 我們發(fā)現(xiàn)之前給 waitStatus賦值過的地方只有一處, 那就是shouldParkAfterFailedAcquire 函數(shù)中將前驅(qū)節(jié)點(diǎn)的 waitStatus設(shè)為Node.SIGNAL, 除此之外, 就沒有了.

然而, 真的沒有了嗎???

其實(shí)還有一處, 那就是新建一個(gè)節(jié)點(diǎn)的時(shí)候, 在addWaiter 函數(shù)中, 當(dāng)我們將一個(gè)新的節(jié)點(diǎn)添加進(jìn)隊(duì)列或者初始化空隊(duì)列的時(shí)候, 都會(huì)新建節(jié)點(diǎn) 而新建的節(jié)點(diǎn)的waitStatus在沒有賦值的情況下都會(huì)初始化為0.

所以當(dāng)一個(gè)head節(jié)點(diǎn)的waitStatus為0說明什么呢, 說明這個(gè)head節(jié)點(diǎn)后面沒有在掛起等待中的后繼節(jié)點(diǎn)了(如果有的話, head的ws就會(huì)被后繼節(jié)點(diǎn)設(shè)為Node.SIGNAL了), 自然也就不要執(zhí)行 unparkSuccessor 操作了.

另外一個(gè)有趣的問題是, 為什么要從尾節(jié)點(diǎn)開始逆向查找, 而不是直接從head節(jié)點(diǎn)往后正向查找, 這樣只要正向找到第一個(gè), 不就可以停止查找了嗎?

首先我們要看到,從后往前找是基于一定條件的:

if (s == null || s.waitStatus > 0)

即后繼節(jié)點(diǎn)不存在,或者后繼節(jié)點(diǎn)取消了排隊(duì),這一條件大多數(shù)條件下是不滿足的。因?yàn)殡m然后繼節(jié)點(diǎn)取消排隊(duì)很正常,但是通過上一篇我們介紹的shouldParkAfterFailedAcquire方法可知,節(jié)點(diǎn)在掛起前,都會(huì)給自己找一個(gè)waitStatus狀態(tài)為SIGNAL的前驅(qū)節(jié)點(diǎn),而跳過那些已經(jīng)cancel掉的節(jié)點(diǎn)。

所以,這個(gè)從后往前找的目的其實(shí)是為了照顧剛剛加入到隊(duì)列中的節(jié)點(diǎn),這就牽涉到我們上一篇特別介紹的“尾分叉”了:

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode); //將當(dāng)前線程包裝成Node
    Node pred = tail;
    // 如果隊(duì)列不為空, 則用CAS方式將當(dāng)前節(jié)點(diǎn)設(shè)為尾節(jié)點(diǎn)
    if (pred != null) {
        node.prev = pred; //step 1, 設(shè)置前驅(qū)節(jié)點(diǎn)
        if (compareAndSetTail(pred, node)) { // step2, 將當(dāng)前節(jié)點(diǎn)設(shè)置成新的尾節(jié)點(diǎn)
            pred.next = node; // step 3, 將前驅(qū)節(jié)點(diǎn)的next屬性指向自己
            return node;
        }
    }
    enq(node); 
    return node;
}

如果你仔細(xì)看上面這段代碼, 可以發(fā)現(xiàn)節(jié)點(diǎn)入隊(duì)不是一個(gè)原子操作, 雖然用了compareAndSetTail操作保證了當(dāng)前節(jié)點(diǎn)被設(shè)置成尾節(jié)點(diǎn),但是只能保證,此時(shí)step1和step2是執(zhí)行完成的,有可能在step3還沒有來(lái)的及執(zhí)行到的時(shí)候,我們的unparkSuccessor方法就開始執(zhí)行了,此時(shí)pred.next的值還沒有被設(shè)置成node,所以從前往后遍歷的話是遍歷不到尾節(jié)點(diǎn)的,但是因?yàn)槲补?jié)點(diǎn)此時(shí)已經(jīng)設(shè)置完成,node.prev = pred操作也被執(zhí)行過了,也就是說,如果從后往前遍歷的話,新加的尾節(jié)點(diǎn)就可以遍歷到了,并且可以通過它一直往前找。

所以總結(jié)來(lái)說,之所以從后往前遍歷是因?yàn)?,我們是處于多線程并發(fā)的條件下的,如果一個(gè)節(jié)點(diǎn)的next屬性為null, 并不能保證它就是尾節(jié)點(diǎn)(可能是因?yàn)樾录拥奈补?jié)點(diǎn)還沒來(lái)得及執(zhí)行pred.next = node), 但是一個(gè)節(jié)點(diǎn)如果能入隊(duì), 則它的prev屬性一定是有值的,所以反向查找一定是最精確的。

最后, 在調(diào)用了 LockSupport.unpark(s.thread) 也就是喚醒了線程之后, 會(huì)發(fā)生什么呢?

當(dāng)然是回到最初的原點(diǎn)啦, 從哪里跌倒(被掛起)就從哪里站起來(lái)(喚醒)唄:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 喏, 就是在這里被掛起了, 喚醒之后就能繼續(xù)往下執(zhí)行了
    return Thread.interrupted();
}

那接下來(lái)做什么呢?

還記得我們上一篇在講“鎖的獲取”的時(shí)候留的問題嗎? 如果線程從這里喚醒了,它將接著往下執(zhí)行。

注意,這里有兩個(gè)線程:
一個(gè)是我們這篇講的線程,它正在釋放鎖,并調(diào)用了LockSupport.unpark(s.thread) 喚醒了另外一個(gè)線程;
而這個(gè)另外一個(gè)線程,就是我們上一節(jié)講的因?yàn)閾屾i失敗而被阻塞在LockSupport.park(this)處的線程。

我們?cè)俚够厣弦黄Y(jié)束的地方,看看這個(gè)被阻塞的線程被喚醒后,會(huì)發(fā)生什么。從上面的代碼可以看出,他將調(diào)用 Thread.interrupted()并返回。

我們知道,Thread.interrupted()這個(gè)函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。接著,我們?cè)俜祷氐?b>parkAndCheckInterrupt被調(diào)用的地方:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 我們?cè)谶@里!在這里??!在這里?。?!
            // 我們?cè)谶@里!在這里??!在這里?。?!
            // 我們?cè)谶@里!在這里??!在這里?。?!
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

具體來(lái)說,就是這個(gè)if語(yǔ)句

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
    interrupted = true;

可見,如果Thread.interrupted()返回true,則 parkAndCheckInterrupt()就返回true, if條件成立,interrupted狀態(tài)將設(shè)為true;
如果Thread.interrupted()返回false, 則 interrupted 仍為false

再接下來(lái)我們又回到了for (;;) 死循環(huán)的開頭,進(jìn)行新一輪的搶鎖。

假設(shè)這次我們搶到了,我們將從 return interrupted處返回,返回到哪里呢? 當(dāng)然是acquireQueued的調(diào)用處啦:

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

我們看到,如果acquireQueued的返回值為true, 我們將執(zhí)行 selfInterrupt():

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

而它的作用,就是中斷當(dāng)前線程。

繞了這么一大圈,到最后還是中斷了當(dāng)前線程,到底是在干嘛呢?

其實(shí)這一切的原因都在于:

我們并不知道線程被喚醒的原因。

具體來(lái)說,當(dāng)我們從LockSupport.park(this)處被喚醒,我們并不知道是因?yàn)槭裁丛虮粏拘眩赡苁且驗(yàn)閯e的線程釋放了鎖,調(diào)用了 LockSupport.unpark(s.thread),也有可能是因?yàn)楫?dāng)前線程在等待中被中斷了,因此我們通過Thread.interrupted()方法檢查了當(dāng)前線程的中斷標(biāo)志,并將它記錄下來(lái),在我們最后返回acquire方法后,如果發(fā)現(xiàn)當(dāng)前線程曾經(jīng)被中斷過,那我們就把當(dāng)前線程再中斷一次。

為什么要這么做呢?

從上面的代碼中我們知道,即使線程在等待資源的過程中被中斷喚醒,它還是會(huì)不依不饒的再搶鎖,直到它搶到鎖為止。也就是說,它是不響應(yīng)這個(gè)中斷的,僅僅是記錄下自己被人中斷過。

最后,當(dāng)它搶到鎖返回了,如果它發(fā)現(xiàn)自己曾經(jīng)被中斷過,它就再中斷自己一次,將這個(gè)中斷補(bǔ)上。

注意,中斷對(duì)線程來(lái)說只是一個(gè)建議,一個(gè)線程被中斷只是其中斷狀態(tài)被設(shè)為true, 線程可以選擇忽略這個(gè)中斷,中斷一個(gè)線程并不會(huì)影響線程的執(zhí)行。

線程中斷是一個(gè)很重要的概念,這個(gè)我們以后有機(jī)會(huì)再細(xì)講。(已成文,參見Thread類源碼解讀(3)——線程中斷interrupt)

最后再小小的插一句,事實(shí)上在我們從return interrupted;處返回時(shí)并不是直接返回的,因?yàn)檫€有一個(gè)finally代碼塊:

finally {
    if (failed)
        cancelAcquire(node);
}

它做了一些善后工作,但是條件是failed為true,而從前面的分析中我們知道,要從for(;;)中跳出來(lái),只有一種可能,那就是當(dāng)前線程已經(jīng)拿到了鎖,因?yàn)檎麄€(gè)爭(zhēng)鎖過程我們都是不響應(yīng)中斷的,所以不可能有異常拋出,既然是拿到了鎖,failed就一定是true,所以這個(gè)finally塊在這里實(shí)際上并沒有什么用,它是為響應(yīng)中斷式的搶鎖所服務(wù)的,這一點(diǎn)我們以后有機(jī)會(huì)再講。

(完)

查看更多系列文章:系列文章目錄

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76474.html

相關(guān)文章

  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    lijy91 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    Yumenokanata 評(píng)論0 收藏0
  • 逐行分析AQS源碼(3)——共享鎖的獲取與釋放

    摘要:而對(duì)于共享鎖而言,由于鎖是可以被共享的,因此它可以被多個(gè)線程同時(shí)持有。換句話說,如果一個(gè)線程成功獲取了共享鎖,那么其他等待在這個(gè)共享鎖上的線程就也可以嘗試去獲取鎖,并且極有可能獲取成功。 前言 前面兩篇我們以ReentrantLock為例了解了AQS獨(dú)占鎖的獲取與釋放,本篇我們來(lái)看看共享鎖。由于AQS對(duì)于共享鎖與獨(dú)占鎖的實(shí)現(xiàn)框架比較類似,因此如果你搞定了前面的獨(dú)占鎖模式,則共享鎖也就很...

    Rindia 評(píng)論0 收藏0
  • 逐行分析AQS源碼(1)——獨(dú)占鎖的獲取

    摘要:本篇我們將以的公平鎖為例來(lái)詳細(xì)看看使用獲取獨(dú)占鎖的流程。本文中的源碼基于。由于本篇我們分析的是獨(dú)占鎖,同一時(shí)刻,鎖只能被一個(gè)線程所持有。由于在整個(gè)搶鎖過程中,我們都是不響應(yīng)中斷的。 前言 AQS(AbstractQueuedSynchronizer)是JAVA中眾多鎖以及并發(fā)工具的基礎(chǔ),其底層采用樂觀鎖,大量使用了CAS操作, 并且在沖突時(shí),采用自旋方式重試,以實(shí)現(xiàn)輕量級(jí)和高效地獲取鎖...

    call_me_R 評(píng)論0 收藏0
  • 逐行分析AQS源碼(4)——Condition接口實(shí)現(xiàn)

    摘要:前言本篇文章是基于線程間的同步與通信和這篇文章寫的,在那篇文章中,我們分析了接口所定義的方法,本篇我們就來(lái)看看對(duì)于接口的這些接口方法的具體實(shí)現(xiàn)。因此,條件隊(duì)列在出隊(duì)時(shí),線程并不持有鎖。 前言 本篇文章是基于線程間的同步與通信(4)——Lock 和 Condtion 這篇文章寫的,在那篇文章中,我們分析了Condition接口所定義的方法,本篇我們就來(lái)看看AQS對(duì)于Condition接口...

    未東興 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<