国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

逐行分析AQS源碼(3)——共享鎖的獲取與釋放

Rindia / 3471人閱讀

摘要:而對(duì)于共享鎖而言,由于鎖是可以被共享的,因此它可以被多個(gè)線程同時(shí)持有。換句話說(shuō),如果一個(gè)線程成功獲取了共享鎖,那么其他等待在這個(gè)共享鎖上的線程就也可以嘗試去獲取鎖,并且極有可能獲取成功。

前言

前面兩篇我們以ReentrantLock為例了解了AQS獨(dú)占鎖的獲取與釋放,本篇我們來(lái)看看共享鎖。由于AQS對(duì)于共享鎖與獨(dú)占鎖的實(shí)現(xiàn)框架比較類(lèi)似,因此如果你搞定了前面的獨(dú)占鎖模式,則共享鎖也就很容易弄懂了。

系列文章目錄

共享鎖與獨(dú)占鎖的區(qū)別

共享鎖與獨(dú)占鎖最大的區(qū)別在于,獨(dú)占鎖是獨(dú)占的,排他的,因此在獨(dú)占鎖中有一個(gè)exclusiveOwnerThread屬性,用來(lái)記錄當(dāng)前持有鎖的線程。當(dāng)獨(dú)占鎖已經(jīng)被某個(gè)線程持有時(shí),其他線程只能等待它被釋放后,才能去爭(zhēng)鎖,并且同一時(shí)刻只有一個(gè)線程能爭(zhēng)鎖成功。

而對(duì)于共享鎖而言,由于鎖是可以被共享的,因此它可以被多個(gè)線程同時(shí)持有。換句話說(shuō),如果一個(gè)線程成功獲取了共享鎖,那么其他等待在這個(gè)共享鎖上的線程就也可以嘗試去獲取鎖,并且極有可能獲取成功。

共享鎖的實(shí)現(xiàn)和獨(dú)占鎖是對(duì)應(yīng)的,我們可以從下面這張表中看出:

獨(dú)占鎖 共享鎖
tryAcquire(int arg) tryAcquireShared(int arg)
tryAcquireNanos(int arg, long nanosTimeout) tryAcquireSharedNanos(int arg, long nanosTimeout)
acquire(int arg) acquireShared(int arg)
acquireQueued(final Node node, int arg) doAcquireShared(int arg)
acquireInterruptibly(int arg) acquireSharedInterruptibly(int arg)
doAcquireInterruptibly(int arg) doAcquireSharedInterruptibly(int arg)
doAcquireNanos(int arg, long nanosTimeout) doAcquireSharedNanos(int arg, long nanosTimeout)
release(int arg) releaseShared(int arg)
tryRelease(int arg) tryReleaseShared(int arg)
- doReleaseShared()

可以看出,除了最后一個(gè)屬于共享鎖的doReleaseShared()方法沒(méi)有對(duì)應(yīng)外,其他的方法,獨(dú)占鎖和共享鎖都是一一對(duì)應(yīng)的。

事實(shí)上,其實(shí)與doReleaseShared()對(duì)應(yīng)的獨(dú)占鎖的方法應(yīng)當(dāng)是unparkSuccessor(h),只是doReleaseShared()邏輯不僅僅包含了unparkSuccessor(h),還包含了其他操作,這一點(diǎn)我們下面分析源碼的時(shí)候再看。

另外,尤其需要注意的是,在獨(dú)占鎖模式中,我們只有在獲取了獨(dú)占鎖的節(jié)點(diǎn)釋放鎖時(shí),才會(huì)喚醒后繼節(jié)點(diǎn)——這是合理的,因?yàn)楠?dú)占鎖只能被一個(gè)線程持有,如果它還沒(méi)有被釋放,就沒(méi)有必要去喚醒它的后繼節(jié)點(diǎn)。

然而,在共享鎖模式下,當(dāng)一個(gè)節(jié)點(diǎn)獲取到了共享鎖,我們?cè)讷@取成功后就可以喚醒后繼節(jié)點(diǎn)了,而不需要等到該節(jié)點(diǎn)釋放鎖的時(shí)候,這是因?yàn)楣蚕礞i可以被多個(gè)線程同時(shí)持有,一個(gè)鎖獲取到了,則后繼的節(jié)點(diǎn)都可以直接來(lái)獲取。因此,在共享鎖模式下,在獲取鎖和釋放鎖結(jié)束時(shí),都會(huì)喚醒后繼節(jié)點(diǎn)。 這一點(diǎn)也是doReleaseShared()方法與unparkSuccessor(h)方法無(wú)法直接對(duì)應(yīng)的根本原因所在。

共享鎖的獲取
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

我們拿它和獨(dú)占鎖模式對(duì)比一下:

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

這兩者的結(jié)構(gòu)看上去似乎有點(diǎn)差別,但事實(shí)上是一樣的,只不過(guò)是共享鎖模式下,將與addWaiter(Node.EXCLUSIVE)對(duì)應(yīng)的addWaiter(Node.SHARED),以及selfInterrupt()操作全部移到了doAcquireShared方法內(nèi)部,這一點(diǎn)我們?cè)谙旅娣治?b>doAcquireShared方法時(shí)就一目了然了。

不過(guò)這里先插一句,相對(duì)于獨(dú)占的鎖的tryAcquire(int arg)返回boolean類(lèi)型的值,共享鎖的tryAcquireShared(int acquires)返回的是一個(gè)整型值:

如果該值小于0,則代表當(dāng)前線程獲取共享鎖失敗

如果該值大于0,則代表當(dāng)前線程獲取共享鎖成功,并且接下來(lái)其他線程嘗試獲取共享鎖的行為很可能成功

如果該值等于0,則代表當(dāng)前線程獲取共享鎖成功,但是接下來(lái)其他線程嘗試獲取共享鎖的行為會(huì)失敗

因此,只要該返回值大于等于0,就表示獲取共享鎖成功。

acquireShared中的tryAcquireShared方法由具體的子類(lèi)負(fù)責(zé)實(shí)現(xiàn),這里我們暫且不表。

接下來(lái)我們看看doAcquireShared方法,它對(duì)應(yīng)于獨(dú)占鎖的acquireQueued,兩者其實(shí)很類(lèi)似,我們把它們相同的部分注釋掉,只看不同的部分:

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    /*boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();*/
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            /*if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }*/
}

關(guān)于上面的if部分,獨(dú)占鎖對(duì)應(yīng)的acquireQueued方法為:

if (p == head && tryAcquire(arg)) {
    setHead(node);
    p.next = null; // help GC
    failed = false;
    return interrupted;
}

因此,綜合來(lái)看,這兩者的邏輯僅有兩處不同:

addWaiter(Node.EXCLUSIVE) -> addWaiter(Node.SHARED)

setHead(node) -> setHeadAndPropagate(node, r)

這里第一點(diǎn)不同就是獨(dú)占鎖的acquireQueued調(diào)用的是addWaiter(Node.EXCLUSIVE),而共享鎖調(diào)用的是addWaiter(Node.SHARED),表明了該節(jié)點(diǎn)處于共享模式,這兩種模式的定義為:

/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

該模式被賦值給了節(jié)點(diǎn)的nextWaiter屬性:

Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

我們知道,在條件隊(duì)列中,nextWaiter是指向條件隊(duì)列中的下一個(gè)節(jié)點(diǎn)的,它將條件隊(duì)列中的節(jié)點(diǎn)串起來(lái),構(gòu)成了單鏈表。但是在sync queue隊(duì)列中,我們只用prev,next屬性來(lái)串聯(lián)節(jié)點(diǎn),形成雙向鏈表,nextWaiter屬性在這里只起到一個(gè)標(biāo)記作用,不會(huì)串聯(lián)節(jié)點(diǎn),這里不要被Node SHARED = new Node()所指向的空節(jié)點(diǎn)迷惑,這個(gè)空節(jié)點(diǎn)并不屬于sync queue,不代表任何線程,它只起到標(biāo)記作用,僅僅用作判斷節(jié)點(diǎn)是否處于共享模式的依據(jù):

// Node#isShard()
final boolean isShared() {
    return nextWaiter == SHARED;
}

這里的第二點(diǎn)不同就在于獲取鎖成功后的行為,對(duì)于獨(dú)占鎖而言,是直接調(diào)用了setHead(node)方法,而共享鎖調(diào)用的是setHeadAndPropagate(node, r)

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);

    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

在該方法內(nèi)部我們不僅調(diào)用了setHead(node),還在一定條件下調(diào)用了doReleaseShared()來(lái)喚醒后繼的節(jié)點(diǎn)。這是因?yàn)?strong>在共享鎖模式下,鎖可以被多個(gè)線程所共同持有,既然當(dāng)前線程已經(jīng)拿到共享鎖了,那么就可以直接通知后繼節(jié)點(diǎn)來(lái)拿鎖,而不必等待鎖被釋放的時(shí)候再通知。

關(guān)于這個(gè)doReleaseShared方法,我們到下面分析鎖釋放的時(shí)候再看。

共享鎖的釋放

我們使用releaseShared(int arg)方法來(lái)釋放共享鎖:

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

該方法對(duì)應(yīng)于獨(dú)占鎖的release(int arg)方法:

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

在獨(dú)占鎖模式下,由于頭節(jié)點(diǎn)就是持有獨(dú)占鎖的節(jié)點(diǎn),在它釋放獨(dú)占鎖后,如果發(fā)現(xiàn)自己的waitStatus不為0,則它將負(fù)責(zé)喚醒它的后繼節(jié)點(diǎn)。

在共享鎖模式下,頭節(jié)點(diǎn)就是持有共享鎖的節(jié)點(diǎn),在它釋放共享鎖后,它也應(yīng)該喚醒它的后繼節(jié)點(diǎn),但是值得注意的是,我們?cè)谥暗?b>setHeadAndPropagate方法中可能已經(jīng)調(diào)用過(guò)該方法了,也就是說(shuō)它可能會(huì)被同一個(gè)頭節(jié)點(diǎn)調(diào)用兩次,也有可能在我們從releaseShared方法中調(diào)用它時(shí),當(dāng)前的頭節(jié)點(diǎn)已經(jīng)易主了,下面我們就來(lái)詳細(xì)看看這個(gè)方法:

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

該方法可能是共享鎖模式最難理解的方法了,在看該方法時(shí),我們需要明確以下幾個(gè)問(wèn)題:

(1) 該方法有幾處調(diào)用?

該方法有兩處調(diào)用,一處在acquireShared方法的末尾,當(dāng)線程成功獲取到共享鎖后,在一定條件下調(diào)用該方法;一處在releaseShared方法中,當(dāng)線程釋放共享鎖的時(shí)候調(diào)用。

(2) 調(diào)用該方法的線程是誰(shuí)?

在獨(dú)占鎖中,只有獲取了鎖的線程才能調(diào)用release釋放鎖,因此調(diào)用unparkSuccessor(h)喚醒后繼節(jié)點(diǎn)的必然是持有鎖的線程,該線程可看做是當(dāng)前的頭節(jié)點(diǎn)(雖然在setHead方法中已經(jīng)將頭節(jié)點(diǎn)的thread屬性設(shè)為了null,但是這個(gè)頭節(jié)點(diǎn)曾經(jīng)代表的就是這個(gè)線程)

在共享鎖中,持有共享鎖的線程可以有多個(gè),這些線程都可以調(diào)用releaseShared方法釋放鎖;而這些線程想要獲得共享鎖,則它們必然曾經(jīng)成為過(guò)頭節(jié)點(diǎn),或者就是現(xiàn)在的頭節(jié)點(diǎn)。因此,如果是在releaseShared方法中調(diào)用的doReleaseShared,可能此時(shí)調(diào)用方法的線程已經(jīng)不是頭節(jié)點(diǎn)所代表的線程了,頭節(jié)點(diǎn)可能已經(jīng)被易主好幾次了。

(3) 調(diào)用該方法的目的是什么?

無(wú)論是在acquireShared中調(diào)用,還是在releaseShared方法中調(diào)用,該方法的目的都是在當(dāng)前共享鎖是可獲取的狀態(tài)時(shí),喚醒head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。這一點(diǎn)看上去和獨(dú)占鎖似乎一樣,但是它們的一個(gè)重要的差別是——在共享鎖中,當(dāng)頭節(jié)點(diǎn)發(fā)生變化時(shí),是會(huì)回到循環(huán)中再立即喚醒head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的。也就是說(shuō),在當(dāng)前節(jié)點(diǎn)完成喚醒后繼節(jié)點(diǎn)的任務(wù)之后將要退出時(shí),如果發(fā)現(xiàn)被喚醒后繼節(jié)點(diǎn)已經(jīng)成為了新的頭節(jié)點(diǎn),則會(huì)立即觸發(fā)喚醒head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的操作,如此周而復(fù)始。

(4) 退出該方法的條件是什么

該方法是一個(gè)自旋操作(for(;;)),退出該方法的唯一辦法是走最后的break語(yǔ)句:

if (h == head)   // loop if head changed
    break;

即,只有在當(dāng)前head沒(méi)有易主時(shí),才會(huì)退出,否則繼續(xù)循環(huán)。
這個(gè)怎么理解呢?
為了說(shuō)明問(wèn)題,這里我們假設(shè)目前sync queue隊(duì)列中依次排列有

dummy node -> A -> B -> C -> D

現(xiàn)在假設(shè)A已經(jīng)拿到了共享鎖,則它將成為新的dummy node,

dummy node (A) -> B -> C -> D

此時(shí),A線程會(huì)調(diào)用doReleaseShared,我們寫(xiě)做doReleaseShared[A],在該方法中將喚醒后繼的節(jié)點(diǎn)B,它很快獲得了共享鎖,成為了新的頭節(jié)點(diǎn):

dummy node (B) -> C -> D

此時(shí),B線程也會(huì)調(diào)用doReleaseShared,我們寫(xiě)做doReleaseShared[B],在該方法中將喚醒后繼的節(jié)點(diǎn)C,但是別忘了,在doReleaseShared[B]調(diào)用的時(shí)候,doReleaseShared[A]還沒(méi)運(yùn)行結(jié)束呢,當(dāng)它運(yùn)行到if(h == head)時(shí),發(fā)現(xiàn)頭節(jié)點(diǎn)現(xiàn)在已經(jīng)變了,所以它將繼續(xù)回到for循環(huán)中,與此同時(shí),doReleaseShared[B]也沒(méi)閑著,它在執(zhí)行過(guò)程中也進(jìn)入到了for循環(huán)中。。。

由此可見(jiàn),我們這里形成了一個(gè)doReleaseShared的“調(diào)用風(fēng)暴”,大量的線程在同時(shí)執(zhí)行doReleaseShared,這極大地加速了喚醒后繼節(jié)點(diǎn)的速度,提升了效率,同時(shí)該方法內(nèi)部的CAS操作又保證了多個(gè)線程同時(shí)喚醒一個(gè)節(jié)點(diǎn)時(shí),只有一個(gè)線程能操作成功。

那如果這里doReleaseShared[A]執(zhí)行結(jié)束時(shí),節(jié)點(diǎn)B還沒(méi)有成為新的頭節(jié)點(diǎn)時(shí),doReleaseShared[A]方法不就退出了嗎?是的,但即使這樣也沒(méi)有關(guān)系,因?yàn)樗呀?jīng)成功喚醒了線程B,即使doReleaseShared[A]退出了,當(dāng)B線程成為新的頭節(jié)點(diǎn)時(shí),doReleaseShared[B]就開(kāi)始執(zhí)行了,它也會(huì)負(fù)責(zé)喚醒后繼節(jié)點(diǎn)的,這樣即使變成這種每個(gè)節(jié)點(diǎn)只喚醒自己后繼節(jié)點(diǎn)的模式,從功能上講,最終也可以實(shí)現(xiàn)喚醒所有等待共享鎖的節(jié)點(diǎn)的目的,只是效率上沒(méi)有之前的“調(diào)用風(fēng)暴”快。

由此我們知道,這里的“調(diào)用風(fēng)暴”事實(shí)上是一個(gè)優(yōu)化操作,因?yàn)樵谖覀儓?zhí)行到該方法的末尾的時(shí)候,unparkSuccessor基本上已經(jīng)被調(diào)用過(guò)了,而由于現(xiàn)在是共享鎖模式,所以被喚醒的后繼節(jié)點(diǎn)極有可能已經(jīng)獲取到了共享鎖,成為了新的head節(jié)點(diǎn),當(dāng)它成為新的head節(jié)點(diǎn)后,它可能還是要在setHeadAndPropagate方法中調(diào)用doReleaseShared喚醒它的后繼節(jié)點(diǎn)。

明確了上面幾個(gè)問(wèn)題后,我們?cè)賮?lái)詳細(xì)分析這個(gè)方法,它最重要的部分就是下面這兩個(gè)if語(yǔ)句:

if (ws == Node.SIGNAL) {
    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
        continue;            // loop to recheck cases
    unparkSuccessor(h);
}
else if (ws == 0 &&
         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
    continue;                // loop on failed CAS

第一個(gè)if很好理解,如果當(dāng)前ws值為Node.SIGNAL,則說(shuō)明后繼節(jié)點(diǎn)需要喚醒,這里采用CAS操作先將Node.SIGNAL狀態(tài)改為0,這是因?yàn)榍懊嬷v過(guò),可能有大量的doReleaseShared方法在同時(shí)執(zhí)行,我們只需要其中一個(gè)執(zhí)行unparkSuccessor(h)操作就行了,這里通過(guò)CAS操作保證了unparkSuccessor(h)只被執(zhí)行一次。

比較難理解的是第二個(gè)else if,首先我們要弄清楚ws啥時(shí)候?yàn)?,一種是上面的compareAndSetWaitStatus(h, Node.SIGNAL, 0)會(huì)導(dǎo)致ws為0,但是很明顯,如果是因?yàn)檫@個(gè)原因,則它是不會(huì)進(jìn)入到else if語(yǔ)句塊的。所以這里的ws為0是指當(dāng)前隊(duì)列的最后一個(gè)節(jié)點(diǎn)成為了頭節(jié)點(diǎn)。為什么是最后一個(gè)節(jié)點(diǎn)呢,因?yàn)槊看涡碌墓?jié)點(diǎn)加進(jìn)來(lái),在掛起前一定會(huì)將自己的前驅(qū)節(jié)點(diǎn)的waitStatus修改成Node.SIGNAL的。(對(duì)這一點(diǎn)不理解的詳細(xì)看這里)

其次,compareAndSetWaitStatus(h, 0, Node.PROPAGATE)這個(gè)操作什么時(shí)候會(huì)失敗?既然這個(gè)操作失敗,說(shuō)明就在執(zhí)行這個(gè)操作的瞬間,ws此時(shí)已經(jīng)不為0了,說(shuō)明有新的節(jié)點(diǎn)入隊(duì)了,ws的值被改為了Node.SIGNAL,此時(shí)我們將調(diào)用continue,在下次循環(huán)中直接將這個(gè)剛剛新入隊(duì)但準(zhǔn)備掛起的線程喚醒。

其實(shí),如果我們?cè)俳Y(jié)合外部的整體條件,就很容易理解這種情況所針對(duì)的場(chǎng)景,不要忘了,進(jìn)入上面這段還有一個(gè)條件是

if (h != null && h != tail)

它處于最外層:

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) { // 注意這里說(shuō)明了隊(duì)列至少有兩個(gè)節(jié)點(diǎn)
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;               
        }
        if (h == head)
            break;
    }
}

這個(gè)條件意味著,隊(duì)列中至少有兩個(gè)節(jié)點(diǎn)。

結(jié)合上面的分析,我們可以看出,這個(gè)

else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

描述了一個(gè)極其嚴(yán)苛且短暫的狀態(tài):

首先,大前提是隊(duì)列里至少有兩個(gè)節(jié)點(diǎn)

其次,要執(zhí)行到else if語(yǔ)句,說(shuō)明我們跳過(guò)了前面的if條件,說(shuō)明頭節(jié)點(diǎn)是剛剛成為頭節(jié)點(diǎn)的,它的waitStatus值還為0,尾節(jié)點(diǎn)是在這之后剛剛加進(jìn)來(lái)的,它需要執(zhí)行shouldParkAfterFailedAcquire,將它的前驅(qū)節(jié)點(diǎn)(即頭節(jié)點(diǎn))的waitStatus值修改為Node.SIGNAL但是目前這個(gè)修改操作還沒(méi)有來(lái)的及執(zhí)行。這種情況使我們得以進(jìn)入else if的前半部分else if (ws == 0 &&

緊接著,要滿足!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)這一條件,說(shuō)明此時(shí)頭節(jié)點(diǎn)的waitStatus已經(jīng)不是0了,這說(shuō)明之前那個(gè)沒(méi)有來(lái)得及執(zhí)行的 shouldParkAfterFailedAcquire將前驅(qū)節(jié)點(diǎn)的的waitStatus值修改為Node.SIGNAL的操作現(xiàn)在執(zhí)行完了。

由此可見(jiàn),else if&& 連接了兩個(gè)不一致的狀態(tài),分別對(duì)應(yīng)了shouldParkAfterFailedAcquirecompareAndSetWaitStatus(pred, ws, Node.SIGNAL)執(zhí)行成功前和執(zhí)行成功后,因?yàn)?b>doReleaseShared和
shouldParkAfterFailedAcquire是可以并發(fā)執(zhí)行的,所以這一條件是有可能滿足的,只是滿足的條件非常嚴(yán)苛,可能只是一瞬間的事。

這里不得不說(shuō),如果以上的分析沒(méi)有錯(cuò)的話,那作者對(duì)于AQS性能的優(yōu)化已經(jīng)到了“令人發(fā)指”的地步!!!雖說(shuō)這種短暫的瞬間確實(shí)存在,也確實(shí)有必要重新回到for循環(huán)中再次去喚醒后繼節(jié)點(diǎn),但是這種優(yōu)化也太太太~~~過(guò)于精細(xì)了吧!

我們來(lái)看看如果不加入這個(gè)精細(xì)的控制條件有什么后果呢?

這里我們復(fù)習(xí)一下新節(jié)點(diǎn)入隊(duì)的過(guò)程,前面說(shuō)過(guò),在發(fā)現(xiàn)新節(jié)點(diǎn)的前驅(qū)不是head節(jié)點(diǎn)的時(shí)候,它將調(diào)用shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don"t park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

由于前驅(qū)節(jié)點(diǎn)的ws值現(xiàn)在還為0,新節(jié)點(diǎn)將會(huì)把它改為Node.SIGNAL,

但修改后,該方法返回的是false,也就是說(shuō)線程不會(huì)立即掛起,而是回到上層再嘗試一次搶鎖:

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // shouldParkAfterFailedAcquire的返回處
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

當(dāng)我們?cè)俅位氐?b>for(;;)循環(huán)中,由于此時(shí)當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)已經(jīng)成為了新的head,所以它可以參與搶鎖,由于它搶的是共享鎖,所以大概率它是搶的到的,所以極有可能它不會(huì)被掛起。這有可能導(dǎo)致在上面的doReleaseShared調(diào)用unparkSuccessor方法unpark了一個(gè)并沒(méi)有被park的線程。然而,這一操作是被允許的,當(dāng)我們unpark一個(gè)并沒(méi)有被park的線程時(shí),該線程在下一次調(diào)用park方法時(shí)就不會(huì)被掛起,而這一行為是符合我們的場(chǎng)景的——因?yàn)楫?dāng)前的共享鎖處于可獲取的狀態(tài),后繼的線程應(yīng)該直接來(lái)獲取鎖,不應(yīng)該被掛起。

事實(shí)上,我個(gè)人認(rèn)為:

else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
    continue;  // loop on failed CAS

這一段其實(shí)也可以省略,當(dāng)然有了這一段肯定會(huì)加速喚醒后繼節(jié)點(diǎn)的過(guò)程,作者針對(duì)上面那種極其短暫的情況進(jìn)行了優(yōu)化可以說(shuō)是和它之前“調(diào)用風(fēng)暴”的設(shè)計(jì)一脈相承,可能也正是由于作者對(duì)于性能的極致追求才使得AQS如此之優(yōu)秀吧。

總結(jié)

共享鎖的調(diào)用框架和獨(dú)占鎖很相似,它們最大的不同在于獲取鎖的邏輯——共享鎖可以被多個(gè)線程同時(shí)持有,而獨(dú)占鎖同一時(shí)刻只能被一個(gè)線程持有。

由于共享鎖同一時(shí)刻可以被多個(gè)線程持有,因此當(dāng)頭節(jié)點(diǎn)獲取到共享鎖時(shí),可以立即喚醒后繼節(jié)點(diǎn)來(lái)爭(zhēng)鎖,而不必等到釋放鎖的時(shí)候。因此,共享鎖觸發(fā)喚醒后繼節(jié)點(diǎn)的行為可能有兩處,一處在當(dāng)前節(jié)點(diǎn)成功獲得共享鎖后,一處在當(dāng)前節(jié)點(diǎn)釋放共享鎖后。

(完)

系列文章目錄

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/77189.html

相關(guān)文章

  • 系列文章目錄

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

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

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

    Yumenokanata 評(píng)論0 收藏0
  • 線程間的同步通信(6)——CountDownLatch源碼分析

    摘要:相較于方法,提供了超時(shí)等待機(jī)制注意,在方法中,我們用到了的返回值,如果該方法因?yàn)槌瑫r(shí)而退出時(shí),則將返回。的這個(gè)返回值有助于我們理解該方法究竟是因?yàn)楂@取到了鎖而返回,還是因?yàn)槌瑫r(shí)時(shí)間到了而返回。 前言 系列文章目錄 CountDownLatch是一個(gè)很有用的工具,latch是門(mén)閂的意思,該工具是為了解決某些操作只能在一組操作全部執(zhí)行完成后才能執(zhí)行的情景。例如,小組早上開(kāi)會(huì),只有等所有人...

    longmon 評(píng)論0 收藏0
  • 逐行分析AQS源碼(2)——獨(dú)占鎖的釋放

    摘要:我們知道,這個(gè)函數(shù)將返回當(dāng)前正在執(zhí)行的線程的中斷狀態(tài),并清除它。注意,中斷對(duì)線程來(lái)說(shuō)只是一個(gè)建議,一個(gè)線程被中斷只是其中斷狀態(tài)被設(shè)為線程可以選擇忽略這個(gè)中斷,中斷一個(gè)線程并不會(huì)影響線程的執(zhí)行。 前言 系列文章目錄 上一篇文章 我們逐行分析了獨(dú)占鎖的獲取操作, 本篇文章我們來(lái)看看獨(dú)占鎖的釋放。如果前面的鎖的獲取流程你已經(jīng)趟過(guò)一遍了, 那鎖的釋放部分就很簡(jiǎn)單了, 這篇文章我們直接開(kāi)始看...

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

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

    call_me_R 評(píng)論0 收藏0

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

0條評(píng)論

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