摘要:而對(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)了shouldParkAfterFailedAcquire的compareAndSetWaitStatus(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ò)長(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)。 為了避免一篇...
摘要:為了避免一篇文章的篇幅過(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)。 為了避免一篇...
摘要:相較于方法,提供了超時(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ì),只有等所有人...
摘要:我們知道,這個(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)始看...
摘要:本篇我們將以的公平鎖為例來(lái)詳細(xì)看看使用獲取獨(dú)占鎖的流程。本文中的源碼基于。由于本篇我們分析的是獨(dú)占鎖,同一時(shí)刻,鎖只能被一個(gè)線程所持有。由于在整個(gè)搶鎖過(guò)程中,我們都是不響應(yīng)中斷的。 前言 AQS(AbstractQueuedSynchronizer)是JAVA中眾多鎖以及并發(fā)工具的基礎(chǔ),其底層采用樂(lè)觀鎖,大量使用了CAS操作, 并且在沖突時(shí),采用自旋方式重試,以實(shí)現(xiàn)輕量級(jí)和高效地獲取鎖...
閱讀 3192·2023-04-26 01:39
閱讀 3345·2023-04-25 18:09
閱讀 1612·2021-10-08 10:05
閱讀 3228·2021-09-22 15:45
閱讀 2758·2019-08-30 15:55
閱讀 2393·2019-08-30 15:54
閱讀 3167·2019-08-30 15:53
閱讀 1324·2019-08-29 12:32