摘要:從上面的代碼可以看出,條件隊(duì)列是建立在鎖基礎(chǔ)上的,而且必須是獨(dú)占鎖原因后面會(huì)通過源碼分析。明天就是國慶長假了,我自己也計(jì)劃出國玩一趟,散散心。提前祝廣大朋友國慶快樂。
相比于獨(dú)占鎖跟共享鎖,AbstractQueuedSynchronizer中的條件隊(duì)列可能被關(guān)注的并不是很多,但它在阻塞隊(duì)列的實(shí)現(xiàn)里起著至關(guān)重要的作用,同時(shí)如果想全面了解AQS,條件隊(duì)列也是必須要學(xué)習(xí)的。
原文地址:http://www.jianshu.com/p/3f8b...
這篇文章會(huì)涉及到AQS中獨(dú)占鎖跟共享鎖的一些知識(shí),如果你已經(jīng)對(duì)這兩塊內(nèi)容很了解了,那就直接往下看。否則在讀本文之前還是建議讀者先去看看我之前寫的兩篇文章溫習(xí)一下。
深入淺出AQS之獨(dú)占鎖模式
深入淺出AQS之共享鎖模式
區(qū)別于前面兩篇文章,可能之前很多人都沒有太在意AQS中的這塊內(nèi)容,所以這篇文章我們先來看下條件隊(duì)列的使用場(chǎng)景:
//首先創(chuàng)建一個(gè)可重入鎖,它本質(zhì)是獨(dú)占鎖 private final ReentrantLock takeLock = new ReentrantLock(); //創(chuàng)建該鎖上的條件隊(duì)列 private final Condition notEmpty = takeLock.newCondition(); //使用過程 public E take() throws InterruptedException { //首先進(jìn)行加鎖 takeLock.lockInterruptibly(); try { //如果隊(duì)列是空的,則進(jìn)行等待 notEmpty.await(); //取元素的操作... //如果有剩余,則喚醒等待元素的線程 notEmpty.signal(); } finally { //釋放鎖 takeLock.unlock(); } //取完元素以后喚醒等待放入元素的線程 }
上面的代碼片段截取自LinkedBlockingQueue,是Java常用的阻塞隊(duì)列之一。
從上面的代碼可以看出,條件隊(duì)列是建立在鎖基礎(chǔ)上的,而且必須是獨(dú)占鎖(原因后面會(huì)通過源碼分析)。
等待條件的過程:
在操作條件隊(duì)列之前首先需要成功獲取獨(dú)占鎖,不然直接在獲取獨(dú)占鎖的時(shí)候已經(jīng)被掛起了。
成功獲取獨(dú)占鎖以后,如果當(dāng)前條件還不滿足,則在當(dāng)前鎖的條件隊(duì)列上掛起,與此同時(shí)釋放掉當(dāng)前獲取的鎖資源。這里可以考慮一下如果不釋放鎖資源會(huì)發(fā)生什么?
如果被喚醒,則檢查是否可以獲取獨(dú)占鎖,否則繼續(xù)掛起。
條件滿足后的喚醒過程(以喚醒一個(gè)節(jié)點(diǎn)為例,也可以喚醒多個(gè)):
把當(dāng)前等待隊(duì)列中的第一個(gè)有效節(jié)點(diǎn)(如果被取消就無效了)加入同步隊(duì)列等待被前置節(jié)點(diǎn)喚醒,如果此時(shí)前置節(jié)點(diǎn)被取消,則直接喚醒該節(jié)點(diǎn)讓它重新在同步隊(duì)列里適當(dāng)?shù)膰L試獲取鎖或者掛起。
注:說到這里必須要解釋一個(gè)知識(shí)點(diǎn),整個(gè)AQS分為兩個(gè)隊(duì)列,一個(gè)同步隊(duì)列,一個(gè)條件隊(duì)列。只有同步隊(duì)列中的節(jié)點(diǎn)才能獲取鎖。前面兩篇獨(dú)占鎖共享鎖文章中提到的加入隊(duì)列就是同步隊(duì)列。條件隊(duì)列中所謂的喚醒是把節(jié)點(diǎn)從條件隊(duì)列移到同步隊(duì)列,讓節(jié)點(diǎn)有機(jī)會(huì)去獲取鎖。
二、源碼深入分析下面的代碼稍微復(fù)雜一點(diǎn),因?yàn)樗紤]了中斷的處理情況。我由于想跟文章開頭的代碼片段保持一致,所以選取了該方法進(jìn)行說明。如果只想看核心邏輯的話,那推薦讀者看看awaitUninterruptibly()方法的源碼。
//條件隊(duì)列入口,參考上面的代碼片段 public final void await() throws InterruptedException { //如果當(dāng)前線程被中斷則直接拋出異常 if (Thread.interrupted()) throw new InterruptedException(); //把當(dāng)前節(jié)點(diǎn)加入條件隊(duì)列 Node node = addConditionWaiter(); //釋放掉已經(jīng)獲取的獨(dú)占鎖資源 int savedState = fullyRelease(node); int interruptMode = 0; //如果不在同步隊(duì)列中則不斷掛起 while (!isOnSyncQueue(node)) { LockSupport.park(this); //中斷處理,另一種跳出循環(huán)的方式 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //走到這里說明節(jié)點(diǎn)已經(jīng)條件滿足被加入到了同步隊(duì)列中或者中斷了 //這個(gè)方法很熟悉吧?就跟獨(dú)占鎖調(diào)用同樣的獲取鎖方法,從這里可以看出條件隊(duì)列只能用于獨(dú)占鎖 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; //走到這里說明已經(jīng)成功獲取到了獨(dú)占鎖,接下來就做些收尾工作 //刪除條件隊(duì)列中被取消的節(jié)點(diǎn) if (node.nextWaiter != null) unlinkCancelledWaiters(); //根據(jù)不同模式處理中斷 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
流程比較復(fù)雜,一步一步來分析,首先看下加入條件隊(duì)列的代碼:
//注:1.與同步隊(duì)列不同,條件隊(duì)列頭尾指針是firstWaiter跟lastWaiter //注:2.條件隊(duì)列是在獲取鎖之后,也就是臨界區(qū)進(jìn)行操作,因此很多地方不用考慮并發(fā) private Node addConditionWaiter() { Node t = lastWaiter; //如果最后一個(gè)節(jié)點(diǎn)被取消,則刪除隊(duì)列中被取消的節(jié)點(diǎn) //至于為啥是最后一個(gè)節(jié)點(diǎn)后面會(huì)分析 if (t != null && t.waitStatus != Node.CONDITION) { //刪除所有被取消的節(jié)點(diǎn) unlinkCancelledWaiters(); t = lastWaiter; } //創(chuàng)建一個(gè)類型為CONDITION的節(jié)點(diǎn)并加入隊(duì)列,由于在臨界區(qū),所以這里不用并發(fā)控制 Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; } //刪除取消節(jié)點(diǎn)的邏輯雖然長,但比較簡單,就不多帶帶說了,就是鏈表刪除 private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; } }
把節(jié)點(diǎn)加入到條件隊(duì)列中以后,接下來要做的就是釋放鎖資源:
//入?yún)⒕褪切聞?chuàng)建的節(jié)點(diǎn),即當(dāng)前節(jié)點(diǎn) final int fullyRelease(Node node) { boolean failed = true; try { //這里這個(gè)取值要注意,獲取當(dāng)前的state并釋放,這從另一個(gè)角度說明必須是獨(dú)占鎖 //可以考慮下這個(gè)邏輯放在共享鎖下面會(huì)發(fā)生什么? int savedState = getState(); //跟獨(dú)占鎖釋放鎖資源一樣,不贅述 if (release(savedState)) { failed = false; return savedState; } else { //如果這里釋放失敗,則拋出異常 throw new IllegalMonitorStateException(); } } finally { //如果釋放鎖失敗,則把節(jié)點(diǎn)取消,由這里就能看出來上面添加節(jié)點(diǎn)的邏輯中只需要判斷最后一個(gè)節(jié)點(diǎn)是否被取消就可以了 if (failed) node.waitStatus = Node.CANCELLED; } }
走到這一步,節(jié)點(diǎn)也加入條件隊(duì)列中了,鎖資源也釋放了,接下來就該掛起了(先忽略中斷處理,單看掛起邏輯):
//如果不在同步隊(duì)列就繼續(xù)掛起(signal操作會(huì)把節(jié)點(diǎn)加入同步隊(duì)列) while (!isOnSyncQueue(node)) { LockSupport.park(this); //中斷處理后面再分析 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //判斷節(jié)點(diǎn)是否在同步隊(duì)列中 final boolean isOnSyncQueue(Node node) { //快速判斷1:節(jié)點(diǎn)狀態(tài)或者節(jié)點(diǎn)沒有前置節(jié)點(diǎn) //注:同步隊(duì)列是有頭節(jié)點(diǎn)的,而條件隊(duì)列沒有 if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //快速判斷2:next字段只有同步隊(duì)列才會(huì)使用,條件隊(duì)列中使用的是nextWaiter字段 if (node.next != null) return true; //上面如果無法判斷則進(jìn)入復(fù)雜判斷 return findNodeFromTail(node); } //注意這里用的是tail,這是因?yàn)闂l件隊(duì)列中的節(jié)點(diǎn)是被加入到同步隊(duì)列尾部,這樣查找更快 //從同步隊(duì)列尾節(jié)點(diǎn)開始向前查找當(dāng)前節(jié)點(diǎn),如果找到則說明在,否則不在 private boolean findNodeFromTail(Node node) { Node t = tail; for (;;) { if (t == node) return true; if (t == null) return false; t = t.prev; } }
如果被喚醒且已經(jīng)被轉(zhuǎn)移到了同步隊(duì)列,則會(huì)執(zhí)行與獨(dú)占鎖一樣的方法acquireQueued()進(jìn)行同步隊(duì)列獨(dú)占獲取。
最后我們來梳理一下里面的中斷邏輯以及收尾工作的代碼:
while (!isOnSyncQueue(node)) { LockSupport.park(this); //這里被喚醒可能是正常的signal操作也可能是中斷 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //這里的判斷邏輯是: //1.如果現(xiàn)在不是中斷的,即正常被signal喚醒則返回0 //2.如果節(jié)點(diǎn)由中斷加入同步隊(duì)列則返回THROW_IE,由signal加入同步隊(duì)列則返回REINTERRUPT private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } //修改節(jié)點(diǎn)狀態(tài)并加入同步隊(duì)列 //該方法返回true表示節(jié)點(diǎn)由中斷加入同步隊(duì)列,返回false表示由signal加入同步隊(duì)列 final boolean transferAfterCancelledWait(Node node) { //這里設(shè)置節(jié)點(diǎn)狀態(tài)為0,如果成功則加入同步隊(duì)列 if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { //與獨(dú)占鎖同樣的加入隊(duì)列邏輯,不贅述 enq(node); return true; } //如果上面設(shè)置失敗,說明節(jié)點(diǎn)已經(jīng)被signal喚醒,由于signal操作會(huì)將節(jié)點(diǎn)加入同步隊(duì)列,我們只需自旋等待即可 while (!isOnSyncQueue(node)) Thread.yield(); return false; }
在把喚醒后的中斷判斷做好以后,看await()中最后一段邏輯:
//在處理中斷之前首先要做的是從同步隊(duì)列中成功獲取鎖資源 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; //由于當(dāng)前節(jié)點(diǎn)可能是由于中斷修改了節(jié)點(diǎn)狀態(tài),所以如果有后繼節(jié)點(diǎn)則執(zhí)行刪除已取消節(jié)點(diǎn)的操作 //如果沒有后繼節(jié)點(diǎn),根據(jù)上面的分析在后繼節(jié)點(diǎn)加入的時(shí)候會(huì)進(jìn)行刪除 if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); //根據(jù)中斷時(shí)機(jī)選擇拋出異?;蛘咴O(shè)置線程中斷狀態(tài) private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) //實(shí)現(xiàn)代碼為:Thread.currentThread().interrupt(); selfInterrupt(); }
至此條件隊(duì)列await操作全部分析完畢。signal()方法相對(duì)容易一些,一起看源碼分析下:
//條件隊(duì)列喚醒入口 public final void signal() { //如果不是獨(dú)占鎖則拋出異常,再次說明條件隊(duì)列只適用于獨(dú)占鎖 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //如果條件隊(duì)列不為空,則進(jìn)行喚醒操作 Node first = firstWaiter; if (first != null) doSignal(first); } //該方法就是把一個(gè)有效節(jié)點(diǎn)從條件隊(duì)列中刪除并加入同步隊(duì)列 //如果失敗則會(huì)查找條件隊(duì)列上等待的下一個(gè)節(jié)點(diǎn)直到隊(duì)列為空 private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) &&(first = firstWaiter) != null); } //將節(jié)點(diǎn)加入同步隊(duì)列 final boolean transferForSignal(Node node) { //修改節(jié)點(diǎn)狀態(tài),這里如果修改失敗只有一種可能就是該節(jié)點(diǎn)被取消,具體看上面await過程分析 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //該方法很熟悉了,跟獨(dú)占鎖入隊(duì)方法一樣,不贅述 Node p = enq(node); //注:這里的p節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn) int ws = p.waitStatus; //如果前置節(jié)點(diǎn)被取消或者修改狀態(tài)失敗則直接喚醒當(dāng)前節(jié)點(diǎn) //此時(shí)當(dāng)前節(jié)點(diǎn)已經(jīng)處于同步隊(duì)列中,喚醒會(huì)進(jìn)行鎖獲取或者正確的掛起操作 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }三、總結(jié)
相比于獨(dú)占鎖跟共享鎖,條件隊(duì)列可能是最不受關(guān)注的了,但由于它是阻塞隊(duì)列實(shí)現(xiàn)的關(guān)鍵組件,還是有必要了解一下其中的原理。其實(shí)我認(rèn)為關(guān)鍵點(diǎn)有兩條,第一是條件隊(duì)列是建立在某個(gè)具體的鎖上面的,第二是條件隊(duì)列跟同步隊(duì)列是兩個(gè)隊(duì)列,前者依賴條件喚醒后者依賴鎖釋放喚醒,了解了這兩點(diǎn)以后搞清楚條件隊(duì)列就不是什么難事了。
至此,Java同步器AQS中三大鎖模式就都分析完了。雖然已經(jīng)盡力思考,盡量寫的清楚,但鑒于水平有限,如果有紕漏的地方,歡迎廣大讀者指正。
明天就是國慶長假了,我自己也計(jì)劃出國玩一趟,散散心。
提前祝廣大朋友國慶快樂。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/67653.html
摘要:原文地址深入淺出之獨(dú)占鎖模式深入淺出之共享鎖模式深入淺出之條件隊(duì)列前面三篇文章如果之前沒有基礎(chǔ)的話看起來會(huì)比較吃力,這篇文章說明一下的基礎(chǔ)知識(shí),方便快速了解。當(dāng)前節(jié)點(diǎn)由于超時(shí)或者中斷被取消,節(jié)點(diǎn)進(jìn)入這個(gè)狀態(tài)以后將保持不變。 之前分析了AQS中的獨(dú)占鎖,共享鎖,條件隊(duì)列三大模塊,現(xiàn)在從結(jié)構(gòu)上來看看AQS各個(gè)組件的情況。 原文地址:http://www.jianshu.com/p/49b8...
摘要:獲取鎖的過程當(dāng)線程調(diào)用申請(qǐng)獲取鎖資源,如果成功,則進(jìn)入臨界區(qū)。如果隊(duì)列中有其他等待鎖資源的線程需要喚醒,則喚醒隊(duì)列中的第一個(gè)等待節(jié)點(diǎn)先入先出。釋放鎖時(shí),如果隊(duì)列中有等待的線程就進(jìn)行喚醒。 每一個(gè)Java工程師應(yīng)該都或多或少了解過AQS,我自己也是前前后后,反反復(fù)復(fù)研究了很久,看了忘,忘了再看,每次都有不一樣的體會(huì)。這次趁著寫博客,打算重新拿出來系統(tǒng)的研究下它的源碼,總結(jié)成文章,便于以后...
摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...
摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...
摘要:與之相關(guān)的方法有三個(gè)原子性地修改都是類型,可見我們可以進(jìn)行,來定義的獲取與釋放從而實(shí)現(xiàn)我們自定義的同步器。 前言 源碼分析我認(rèn)為主要有兩個(gè)作用:滿足好奇心,我想每一個(gè)有追求的人都不會(huì)滿足于僅僅做一個(gè)API Caller實(shí)現(xiàn)功能就好,我們也想知道它到底是怎么實(shí)現(xiàn)的;借鑒與升華,當(dāng)我們明白了一個(gè)類的設(shè)計(jì)原理,在一定的情境下我們可以借鑒其設(shè)計(jì)哲學(xué),甚至針對(duì)我們自己特殊的業(yè)務(wù)場(chǎng)景對(duì)其進(jìn)行改良與...
閱讀 791·2019-08-29 12:49
閱讀 3581·2019-08-29 11:32
閱讀 3484·2019-08-26 10:43
閱讀 2429·2019-08-23 16:53
閱讀 2079·2019-08-23 15:56
閱讀 1726·2019-08-23 12:03
閱讀 2798·2019-08-23 11:25
閱讀 2108·2019-08-22 15:11