這篇文章給大家分享的是有關(guān)Java并發(fā)中AQS原理的示例分析的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
創(chuàng)新互聯(lián)科技有限公司專(zhuān)業(yè)互聯(lián)網(wǎng)基礎(chǔ)服務(wù)商,為您提供資陽(yáng)托管服務(wù)器,高防主機(jī),成都IDC機(jī)房托管,成都主機(jī)托管等互聯(lián)網(wǎng)服務(wù)。
1、線(xiàn)程阻塞原語(yǔ)
Java 的線(xiàn)程阻塞和喚醒是通過(guò) Unsafe 類(lèi)的 park 和 unpark 方法做到的。
這兩個(gè)方法都是 native 方法,它們本身是由 C 語(yǔ)言來(lái)實(shí)現(xiàn)的核心功能。park 的意思是停車(chē),讓當(dāng)前運(yùn)行的線(xiàn)程 Thread.currentThread() 休眠,unpark 的意思是解除停車(chē),喚醒指定線(xiàn)程。
這兩個(gè)方法在底層是使用操作系統(tǒng)提供的信號(hào)量機(jī)制來(lái)實(shí)現(xiàn)的。具體實(shí)現(xiàn)過(guò)程要深究 C 代碼,這里暫時(shí)不去具體分析。park 方法的兩個(gè)參數(shù)用來(lái)控制休眠多長(zhǎng)時(shí)間,第一個(gè)參數(shù) isAbsolute 表示第二個(gè)參數(shù)是絕對(duì)時(shí)間還是相對(duì)時(shí)間,單位是毫秒。
線(xiàn)程從啟動(dòng)開(kāi)始就會(huì)一直跑,除了操作系統(tǒng)的任務(wù)調(diào)度策略外,它只有在調(diào)用 park 的時(shí)候才會(huì)暫停運(yùn)行。鎖可以暫停線(xiàn)程的奧秘所在正是因?yàn)殒i在底層調(diào)用了 park 方法。
2、parkBlocker
線(xiàn)程對(duì)象 Thread 里面有一個(gè)重要的屬性 parkBlocker,它保存當(dāng)前線(xiàn)程因?yàn)槭裁炊? park。就好比停車(chē)場(chǎng)上停了很多車(chē),這些車(chē)主都是來(lái)參加一場(chǎng)拍賣(mài)會(huì)的,等拍下自己想要的物品后,就把車(chē)開(kāi)走。那么這里的 parkBlocker 大約就是指這場(chǎng)「拍賣(mài)會(huì)」。它是一系列沖突線(xiàn)程的管理者協(xié)調(diào)者,哪個(gè)線(xiàn)程該休眠該喚醒都是由它來(lái)控制的。
當(dāng)線(xiàn)程被 unpark 喚醒后,這個(gè)屬性會(huì)被置為 null。Unsafe.park 和 unpark 并不會(huì)幫我們?cè)O(shè)置 parkBlocker 屬性,負(fù)責(zé)管理這個(gè)屬性的工具類(lèi)是 LockSupport,它對(duì) Unsafe 這兩個(gè)方法進(jìn)行了簡(jiǎn)單的包裝。
Java 的鎖數(shù)據(jù)結(jié)構(gòu)正是通過(guò)調(diào)用 LockSupport 來(lái)實(shí)現(xiàn)休眠與喚醒的。線(xiàn)程對(duì)象里面的 parkBlocker 字段的值就是下面我們要講的「排隊(duì)管理器」。
3、排隊(duì)管理器
當(dāng)多個(gè)線(xiàn)程爭(zhēng)用同一把鎖時(shí),必須有排隊(duì)機(jī)制將那些沒(méi)能拿到鎖的線(xiàn)程串在一起。當(dāng)鎖釋放時(shí),鎖管理器就會(huì)挑選一個(gè)合適的線(xiàn)程來(lái)占有這個(gè)剛剛釋放的鎖。
每一把鎖內(nèi)部都會(huì)有這樣一個(gè)隊(duì)列管理器,管理器里面會(huì)維護(hù)一個(gè)等待的線(xiàn)程隊(duì)列。ReentrantLock 里面的隊(duì)列管理器是 AbstractQueuedSynchronizer,它內(nèi)部的等待隊(duì)列是一個(gè)雙向列表結(jié)構(gòu)。
加鎖不成功時(shí),當(dāng)前的線(xiàn)程就會(huì)把自己納入到等待鏈表的尾部,然后調(diào)用 LockSupport.park 將自己休眠。其它線(xiàn)程解鎖時(shí),會(huì)從鏈表的表頭取一個(gè)節(jié)點(diǎn),調(diào)用 LockSupport.unpark 喚醒它。
AbstractQueuedSynchronizer 類(lèi)是一個(gè)抽象類(lèi),它是所有的鎖隊(duì)列管理器的父類(lèi),JDK 中的各種形式的鎖其內(nèi)部的隊(duì)列管理器都繼承了這個(gè)類(lèi),它是 Java 并發(fā)世界的核心基石。
比如 ReentrantLock、ReadWriteLock、CountDownLatch、Semaphore、ThreadPoolExecutor 內(nèi)部的隊(duì)列管理器都是它的子類(lèi)。這個(gè)抽象類(lèi)暴露了一些抽象方法,每一種鎖都需要對(duì)這個(gè)管理器進(jìn)行定制。而 JDK 內(nèi)置的所有并發(fā)數(shù)據(jù)結(jié)構(gòu)都是在這些鎖的保護(hù)下完成的,它是JDK 多線(xiàn)程高樓大廈的地基。
鎖管理器維護(hù)的只是一個(gè)普通的雙向列表形式的隊(duì)列,這個(gè)數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單,但是仔細(xì)維護(hù)起來(lái)卻相當(dāng)復(fù)雜,因?yàn)樗枰?xì)考慮多線(xiàn)程并發(fā)問(wèn)題,每一行代碼都寫(xiě)的無(wú)比小心。
JDK 鎖管理器的實(shí)現(xiàn)者是 Douglas S. Lea,Java 并發(fā)包幾乎全是他單槍匹馬寫(xiě)出來(lái)的,在算法的世界里越是精巧的東西越是適合一個(gè)人來(lái)做。
后面我們將 AbstractQueuedSynchronizer 簡(jiǎn)寫(xiě)成 AQS。我必須提醒各位讀者,AQS 太復(fù)雜了,如果在理解它的路上遇到了挫折,這很正常。目前市場(chǎng)上并不存在一本可以輕松理解 AQS 的書(shū)籍,能夠吃透 AQS 的人太少太少,我自己也不算。
4、公平鎖與非公平鎖
公平鎖會(huì)確保請(qǐng)求鎖和獲得鎖的順序,如果在某個(gè)點(diǎn)鎖正處于自由狀態(tài),這時(shí)有一個(gè)線(xiàn)程要嘗試加鎖,公平鎖還必須查看當(dāng)前有沒(méi)有其它線(xiàn)程排在排隊(duì),而非公平鎖可以直接插隊(duì)。聯(lián)想一下在肯德基買(mǎi)漢堡時(shí)的排隊(duì)場(chǎng)景。
也許你會(huì)問(wèn),如果某個(gè)鎖處于自由狀態(tài),那它怎么會(huì)有排隊(duì)的線(xiàn)程呢?我們假設(shè)此刻持有鎖的線(xiàn)程剛剛釋放了鎖,它喚醒了等待隊(duì)列中第一個(gè)節(jié)點(diǎn)線(xiàn)程,這時(shí)候被喚醒的線(xiàn)程剛剛從 park 方法返回,接下來(lái)它就會(huì)嘗試去加鎖,那么從 park 返回到加鎖之間的狀態(tài)就是鎖的自由態(tài),這很短暫,而這短暫的時(shí)間內(nèi)還可能有其它線(xiàn)程也在嘗試加鎖。
其次還有一點(diǎn)需要注意,執(zhí)行了 Lock.park 方法的線(xiàn)程自我休眠后,并不是非要等到其它線(xiàn)程 unpark 了自己才會(huì)醒來(lái),它可能隨時(shí)會(huì)以某種未知的原因醒來(lái)。我們看源碼注釋?zhuān)琾ark 返回的原因有四種:
①其它線(xiàn)程 unpark 了當(dāng)前線(xiàn)程;
②時(shí)間到了自然醒(park 有時(shí)間參數(shù));
③其它線(xiàn)程 interrupt 了當(dāng)前線(xiàn)程;
④其它未知原因?qū)е碌摹讣傩选?
文檔中沒(méi)有明確說(shuō)明何種未知原因會(huì)導(dǎo)致假醒,它倒是說(shuō)明了當(dāng) park 方法返回時(shí)并不意味著鎖自由了,醒過(guò)來(lái)的線(xiàn)程在重新嘗試獲取鎖失敗后將會(huì)再次 park 自己。所以加鎖的過(guò)程需要寫(xiě)在一個(gè)循環(huán)里,在成功拿到鎖之前可能會(huì)進(jìn)行多次嘗試。
計(jì)算機(jī)世界非公平鎖的服務(wù)效率要高于公平鎖,所以 Java 默認(rèn)的鎖都使用了非公平鎖。不過(guò)現(xiàn)實(shí)世界似乎非公平鎖的效率會(huì)差一點(diǎn),比如在肯德基如果可以不停插隊(duì),你可以想象現(xiàn)場(chǎng)肯定一片混亂。為什么計(jì)算機(jī)世界和現(xiàn)實(shí)世界會(huì)有差異,大概是因?yàn)樵谟?jì)算機(jī)世界里某個(gè)線(xiàn)程插隊(duì)并不會(huì)導(dǎo)致其它線(xiàn)程抱怨。
5、共享鎖與排他鎖
ReentrantLock 的鎖是排他鎖,一個(gè)線(xiàn)程持有,其它線(xiàn)程都必須等待。而 ReadWriteLock 里面的讀鎖不是排他鎖,它允許多線(xiàn)程同時(shí)持有讀鎖,這是共享鎖。共享鎖和排他鎖是通過(guò) Node 類(lèi)里面的 nextWaiter 字段區(qū)分的。
那為什么這個(gè)字段沒(méi)有命名成 mode 或者 type 或者干脆直接叫 shared?這是因?yàn)?nextWaiter 在其它場(chǎng)景還有不一樣的用途,它就像 C 語(yǔ)言聯(lián)合類(lèi)型的字段一樣隨機(jī)應(yīng)變,只不過(guò) Java 語(yǔ)言沒(méi)有聯(lián)合類(lèi)型。
6、條件變量
關(guān)于條件變量,需要提出的第一個(gè)問(wèn)題是為什么需要條件變量,只有鎖還不夠么?考慮下面的偽代碼,當(dāng)某個(gè)條件滿(mǎn)足時(shí),才去干某件事
當(dāng)條件不滿(mǎn)足時(shí),就循環(huán)重試(其它線(xiàn)程會(huì)通過(guò)加鎖來(lái)修改條件),但是需要間隔 sleep,不然 CPU 就會(huì)因?yàn)榭辙D(zhuǎn)而飆高。這里存在一個(gè)問(wèn)題,那就是 sleep 多久不好控制。間隔太久,會(huì)拖慢整體效率,甚至?xí)e(cuò)過(guò)時(shí)機(jī)(條件瞬間滿(mǎn)足了又立即被重置了),間隔太短,又回導(dǎo)致 CPU 空轉(zhuǎn)。有了條件變量,這個(gè)問(wèn)題就可以解決了
await() 方法會(huì)一直阻塞在 cond 條件變量上直到被另外一個(gè)線(xiàn)程調(diào)用了 cond.signal() 或者 cond.signalAll() 方法后才會(huì)返回,await() 阻塞時(shí)會(huì)自動(dòng)釋放當(dāng)前線(xiàn)程持有的鎖,await() 被喚醒后會(huì)再次嘗試持有鎖(可能又需要排隊(duì)),拿到鎖成功之后 await() 方法才能成功返回。
阻塞在條件變量上的線(xiàn)程可以有多個(gè),這些阻塞線(xiàn)程會(huì)被串聯(lián)成一個(gè)條件等待隊(duì)列。當(dāng) signalAll() 被調(diào)用時(shí),會(huì)喚醒所有的阻塞線(xiàn)程,讓所有的阻塞線(xiàn)程重新開(kāi)始爭(zhēng)搶鎖。如果調(diào)用的是 signal() 只會(huì)喚醒隊(duì)列頭部的線(xiàn)程,這樣可以避免「驚群?jiǎn)栴}」。
await() 方法必須立即釋放鎖,否則臨界區(qū)狀態(tài)就不能被其它線(xiàn)程修改,condition_is_true() 返回的結(jié)果也就不會(huì)改變。 這也是為什么條件變量必須由鎖對(duì)象來(lái)創(chuàng)建,條件變量需要持有鎖對(duì)象的引用這樣才可以釋放鎖以及被 signal 喚醒后重新加鎖。
創(chuàng)建條件變量的鎖必須是排他鎖,如果是共享鎖被 await() 方法釋放了并不能保證臨界區(qū)的狀態(tài)可以被其它線(xiàn)程來(lái)修改,可以修改臨界區(qū)狀態(tài)的只能是排他鎖。
有了條件變量,sleep 不好控制的問(wèn)題就解決了。當(dāng)條件滿(mǎn)足時(shí),調(diào)用 signal() 或者 signalAll() 方法,阻塞的線(xiàn)程可以立即被喚醒,幾乎沒(méi)有任何延遲。
7、條件等待隊(duì)列
當(dāng)多個(gè)線(xiàn)程 await() 在同一個(gè)條件變量上時(shí),會(huì)形成一個(gè)條件等待隊(duì)列。同一個(gè)鎖可以創(chuàng)建多個(gè)條件變量,就會(huì)存在多個(gè)條件等待隊(duì)列。這個(gè)隊(duì)列和 AQS 的隊(duì)列結(jié)構(gòu)很接近,只不過(guò)它不是雙向隊(duì)列,而是單向隊(duì)列。隊(duì)列中的節(jié)點(diǎn)和 AQS 等待隊(duì)列的節(jié)點(diǎn)是同一個(gè)類(lèi),但是節(jié)點(diǎn)指針不是 prev 和 next,而是 nextWaiter。
ConditionObject 是 AQS 的內(nèi)部類(lèi),這個(gè)對(duì)象里會(huì)有一個(gè)隱藏的指針 this$0 指向外部的 AQS 對(duì)象,ConditionObject 可以直接訪(fǎng)問(wèn) AQS 對(duì)象的所有屬性和方法(加鎖解鎖)。位于條件等待隊(duì)列里的所有節(jié)點(diǎn)的 waitStatus 狀態(tài)都被標(biāo)記為 CONDITION,表示節(jié)點(diǎn)是因?yàn)闂l件變量而等待。
8、隊(duì)列轉(zhuǎn)移
當(dāng)條件變量的 signal() 方法被調(diào)用時(shí),條件等待隊(duì)列的頭節(jié)點(diǎn)線(xiàn)程會(huì)被喚醒,該節(jié)點(diǎn)從條件等待隊(duì)列中被摘走,然后被轉(zhuǎn)移到 AQS 的等待隊(duì)列中,準(zhǔn)備排隊(duì)嘗試重新獲取鎖。這時(shí)節(jié)點(diǎn)的狀態(tài)從 CONDITION 轉(zhuǎn)為 SIGNAL,表示當(dāng)前節(jié)點(diǎn)是被條件變量喚醒轉(zhuǎn)移過(guò)來(lái)的。
被轉(zhuǎn)移的節(jié)點(diǎn)的 nextWaiter 字段的含義也發(fā)生了變更,在條件隊(duì)列里它是下一個(gè)節(jié)點(diǎn)的指針,在 AQS 等待隊(duì)列里它是共享鎖還是互斥鎖的標(biāo)志。
9、讀寫(xiě)鎖
讀寫(xiě)鎖分為兩個(gè)鎖對(duì)象 ReadLock 和 WriteLock,這兩個(gè)鎖對(duì)象共享同一個(gè) AQS。AQS 的鎖計(jì)數(shù)變量 state 將分為兩個(gè)部分,前 16bit 為共享鎖 ReadLock 計(jì)數(shù),后 16bit 為互斥鎖 WriteLock 計(jì)數(shù)。互斥鎖記錄的是當(dāng)前寫(xiě)鎖重入的次數(shù),共享鎖記錄的是所有當(dāng)前持有共享讀鎖的線(xiàn)程重入總次數(shù)。
讀寫(xiě)鎖同樣也需要考慮公平鎖和非公平鎖。共享鎖和互斥鎖的公平鎖策略和 ReentrantLock 一樣,就是看看當(dāng)前還有沒(méi)有其它線(xiàn)程在排隊(duì),自己會(huì)乖乖排到隊(duì)尾。非公平鎖策略不一樣,它會(huì)比較偏向于給寫(xiě)鎖提供更多的機(jī)會(huì)。
如果當(dāng)前 AQS 隊(duì)列里有任何讀寫(xiě)請(qǐng)求的線(xiàn)程在排隊(duì),那么寫(xiě)鎖可以直接去爭(zhēng)搶?zhuān)侨绻?duì)頭是寫(xiě)鎖請(qǐng)求,那么讀鎖需要將機(jī)會(huì)讓給寫(xiě)鎖,去隊(duì)尾排隊(duì)。 畢竟讀寫(xiě)鎖適合讀多寫(xiě)少的場(chǎng)合,對(duì)于偶爾出現(xiàn)一個(gè)寫(xiě)鎖請(qǐng)求就應(yīng)該得到更高的優(yōu)先級(jí)去處理。
感謝各位的閱讀!關(guān)于“Java并發(fā)中AQS原理的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
網(wǎng)頁(yè)題目:Java并發(fā)中AQS原理的示例分析
網(wǎng)站路徑:http://www.chinadenli.net/article18/ieoggp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、外貿(mào)建站、品牌網(wǎng)站制作、網(wǎng)站維護(hù)、企業(yè)網(wǎng)站制作、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)