ArrayBlockingQueue源碼解讀

雖然在某程序員最喜歡的工具調(diào)查中起趾,90%的人選擇了eclipse,但是我這里還是要強(qiáng)烈推薦idea.我在來(lái)我目前公司之前也是用eclipse,但是公司里的程序員用的都是idea,然后自己也嘗試轉(zhuǎn)型,一用上以后發(fā)現(xiàn)我已經(jīng)深深?lèi)?ài)上它了,真的是太好用了。尤其是讀源碼和源碼debug方面,真的是非常便捷,在這里強(qiáng)烈推薦吹截。有人說(shuō)這個(gè)要收費(fèi),我想說(shuō)呵呵凝危,我自己也是百度的2016破解波俄,可惜沒(méi)有做記錄,目前網(wǎng)上還是有注冊(cè)服務(wù)器的蛾默,你需要多一點(diǎn)耐心就好懦铺。

好了廢話(huà)不多說(shuō),開(kāi)搞支鸡。今天讀的源碼的是ArrayBlockingQueue冬念。首先來(lái)看一張類(lèi)接口關(guān)系圖。


層次關(guān)系圖

其實(shí)這個(gè)類(lèi)主要的方法是在BlockingQuere接口中定義好的牧挣。所有的實(shí)現(xiàn)除了add方法調(diào)用了abstarctQueue之外急前,其他都是自己實(shí)現(xiàn)的。下面我們來(lái)看看他是怎么實(shí)現(xiàn)的瀑构。


類(lèi)的全局變量


第一個(gè)final的object數(shù)組即是實(shí)現(xiàn)的容器裆针,這里我們要強(qiáng)調(diào)一下,如果你了解類(lèi)的加載過(guò)程,那你就對(duì)這個(gè)一點(diǎn)也不奇怪了据块,如果不懂你可以去看一下類(lèi)的加載過(guò)程码邻,這里的final沒(méi)有被static修飾折剃,也就代表著它的初始化是在new對(duì)象的Init方法里初始化的另假。init也就是我們的構(gòu)造函數(shù)執(zhí)行的過(guò)程。

takeindex代表著下一個(gè)可取的數(shù)組下標(biāo)怕犁。初始化為0边篮,然后調(diào)用poll,remove奏甫,take方法都會(huì)進(jìn)行++操作戈轿,如果>=數(shù)組容量長(zhǎng)度,則重置為0

putindex代表著下一個(gè)可存放的數(shù)組下標(biāo)阵子,初始化為0思杯,如果調(diào)用add,offer,put則進(jìn)行++操作,如果>=數(shù)組容量長(zhǎng)度挠进,則重置為0

count代表總共存放了多少個(gè)對(duì)象色乾,

下面維護(hù)了一把鎖,2個(gè)condition领突,notempty用于維護(hù)的是空阻塞暖璧,這里比如調(diào)用poll和take的時(shí)候如果count為0則會(huì)進(jìn)行阻塞,只要添加一個(gè)君旦,則會(huì)執(zhí)行notempty.signl操作喚醒阻塞澎办,具體的情況下會(huì)講解。notFull維護(hù)的是滿(mǎn)阻塞金砍,調(diào)用offer和put如果數(shù)組滿(mǎn)了則會(huì)阻塞局蚀。

上面這些就組成了我們實(shí)現(xiàn)這個(gè)隊(duì)列的所有必要的元素。

當(dāng)然這里重點(diǎn)講解的是重點(diǎn)幾個(gè)API實(shí)現(xiàn)的過(guò)程恕稠。下面我們一個(gè)一個(gè)的展開(kāi)講至会。

首先我們來(lái)看下構(gòu)造函數(shù),


構(gòu)造函數(shù)

其中第一個(gè)需要指定初始化大小谱俭,這里的數(shù)組大小一旦指定是不可變的奉件。


默認(rèn)初始化的鎖為非公平鎖,如果不懂公平鎖非公平鎖昆著,請(qǐng)自行查看


如果需要指定公平鎖县貌,則使用2個(gè)參數(shù)的構(gòu)造函數(shù),下面看三個(gè)參數(shù)的


初始化動(dòng)作也是調(diào)用了2個(gè)參數(shù)的凑懂,但是多了一步煤痕,我們可以把別的集合直接轉(zhuǎn)到這個(gè)隊(duì)列中去。

============================下面看方法實(shí)現(xiàn)================================

首先看add、put摆碉、offer方法塘匣。首先說(shuō)下三個(gè)方法的區(qū)別:

add,添加失敗會(huì)拋出異常

put巷帝,如果數(shù)組滿(mǎn)則會(huì)阻塞到有空位為止

offer忌卤,添加失敗會(huì)返回false。

下面先來(lái)看add方法楞泼。


add方法直接調(diào)用了abstractQueue中的add方法驰徊,


從這里看為什么我們add方法會(huì)拋出異常,原來(lái)就是這么來(lái)的堕阔,如果添加失敗則拋出異常棍厂。添加動(dòng)作能交給offer去做,添加失敗返回false超陆,然后就拋出異常牺弹,其實(shí)就是對(duì)offer的返回形式進(jìn)行了一次改變而已嘛!

下面我們看offer操作

offer提供了2個(gè)操作时呀,一個(gè)是只有一個(gè)待添加元素的參數(shù)张漂,另外一個(gè)是多了2個(gè)參數(shù),一個(gè)是long類(lèi)型基本數(shù)據(jù)類(lèi)型退唠,一個(gè)是jdk1.8的新的時(shí)間對(duì)象工具鹃锈。不要著急,我們一個(gè)個(gè)來(lái)看瞧预。



先來(lái)看1個(gè)參數(shù)的:


一進(jìn)來(lái)首先對(duì)它進(jìn)行null值檢測(cè)屎债,其實(shí)實(shí)現(xiàn)很簡(jiǎn)單,就是如果為null拋出空指針異常垢油。

然后鎖定盆驹,判斷,最終添加操作是在enqueue這個(gè)操作里實(shí)現(xiàn)的滩愁。其實(shí)三種添加操作都是通過(guò)enqueue方法實(shí)現(xiàn)的躯喇,所有的取出操作都是dequeue方法實(shí)現(xiàn)的。然后如果成功返回true,如果失敗返回false.就是這么實(shí)現(xiàn)的硝枉。


添加操作廉丽。

這里的實(shí)現(xiàn)很簡(jiǎn)單,直接放入下一個(gè)可存放的位置妻味,然后對(duì)下一個(gè)可存放下標(biāo)++操作正压,然后與數(shù)組容量判斷,如果滿(mǎn)了則置為0责球,然后對(duì)記錄總存量的count進(jìn)行++操作焦履。然后調(diào)用非空鎖喚醒操作拓劝。這里留一個(gè)疑問(wèn),比如我當(dāng)前添加滿(mǎn)了嘉裤,然后下一個(gè)可存放的位置重置為0郑临,這時(shí)候另外一個(gè)操作按照下標(biāo)取走了中間一個(gè)位置的對(duì)象,或者是末尾位置的對(duì)象屑宠,這時(shí)候數(shù)組是有空位置的厢洞,這時(shí)候我們?cè)俅嫒胍粋€(gè)時(shí)候會(huì)發(fā)生什么事情呢

下面我們看待時(shí)間參數(shù)的offer方法


第一個(gè)參數(shù)是待存入元素侨把,第二個(gè)參數(shù)是超時(shí)時(shí)間犀变,第三個(gè)參數(shù)是時(shí)間單位妹孙。

通過(guò)上面的while循環(huán)可知秋柄,如果阻塞超時(shí),則直接返回false蠢正,如果阻塞期間骇笔,count變化了,則進(jìn)入下面的enqueue方法存入對(duì)象嚣崭。實(shí)現(xiàn)就這么簡(jiǎn)單笨触。

還剩最后一個(gè)我們看put方法,


put方法就是在while循環(huán)里阻塞雹舀,然后等待喚醒芦劣。這里的lock鎖是通過(guò)lockInterruptibly();進(jìn)行獲取的,這里我寫(xiě)這的時(shí)候也不明白為什么這么寫(xiě)说榆。后面會(huì)研究下關(guān)于鎖的獲取的不同方式虚吟。

這里我們已經(jīng)把添加元素的方法都看完了,是不是很簡(jiǎn)單啊签财。

下面我們看取的幾個(gè)實(shí)現(xiàn)串慰,包括take,poll唱蒸,peek邦鲫。

take方法是阻塞獲取,如果為空神汹,則阻塞庆捺,直到有對(duì)象了再獲取,從數(shù)組中刪除該對(duì)象

poll方法是如果為空返回null屁魏,從數(shù)組中刪除該對(duì)象

peek方法是直接返回該對(duì)象滔以,并不會(huì)從數(shù)組中刪除

先來(lái)看take的實(shí)現(xiàn)方式



很簡(jiǎn)單,如果為空則阻塞蚁堤,一旦有元素則喚醒醉者,然后調(diào)用dequeue方法獲取但狭,我們看看dequeue的實(shí)現(xiàn)


這里先把下一個(gè)可獲取元素位置的下標(biāo)取出來(lái),然后把這個(gè)位置修改為null撬即,然后在進(jìn)行對(duì)可獲取下標(biāo)位置的修改立磁,如果==數(shù)組容量則重置為0.然后對(duì)非滿(mǎn)進(jìn)行喚醒操作。


poll方法有2個(gè)實(shí)現(xiàn)剥槐,一個(gè)是如果為空立即返回null唱歧,另外一個(gè)就是阻塞等待時(shí)間,如果超時(shí)立即返回粒竖。


用了一個(gè)三目運(yùn)算符颅崩,soeasy


這里也會(huì)在while循環(huán)里進(jìn)行阻塞,如果超時(shí)返回null蕊苗,如果有新的對(duì)象進(jìn)來(lái)沿后,則調(diào)用dequeue。

下面看peek方法


peek直接返回了下一個(gè)可取位置的對(duì)象朽砰,不進(jìn)行刪除操作尖滚。

到此為止,我們的添加和獲取都看過(guò)了瞧柔,下面來(lái)看remove方法漆弄,有4個(gè)方法

remove(o),刪除指定對(duì)象

remove()刪除一個(gè)

removeAll刪除集合中的全部對(duì)象

removeIf()


先來(lái)看不帶參數(shù)的造锅,remove方法直接返回了當(dāng)前移除的對(duì)象撼唾,刪除的呢正好是下一個(gè)可獲取的對(duì)象。


移除操作實(shí)際調(diào)用的是abstractQueue中的remove,最后實(shí)際調(diào)用的還是poll方法哥蔚。

下面看刪除指定對(duì)象


這里首先我們找出需要?jiǎng)h除對(duì)象的下標(biāo)倒谷,通過(guò)指定下標(biāo)取刪除

下面看看刪除指定位置的,

如果指定刪除的下標(biāo)正好等于可獲取的下一個(gè)下標(biāo)肺素,則直接把該位置的修改為null恨锚,然后對(duì)下一個(gè)可獲取的下標(biāo)進(jìn)行++操作,如果等于數(shù)組長(zhǎng)度倍靡,則重置為0猴伶;


如果指定位置的下標(biāo)不等于下一個(gè)可獲取位置的下標(biāo),則在一個(gè)for循環(huán)里塌西,從指定刪除位置下標(biāo)開(kāi)始他挎,把下一個(gè)對(duì)象前移一位


這里我們假設(shè)目前5個(gè)大小的數(shù)組是滿(mǎn)的,下一個(gè)可存放下標(biāo)重置為0了捡需,然后待移除的下標(biāo) i 位置為2办桨,則next=2+1,然后把下標(biāo)為3的移動(dòng)到下標(biāo)為2的站辉,然后把 i=next呢撞,循環(huán)损姜,然后當(dāng)i=4的時(shí)候,我們的next=5與數(shù)組容量一樣大小了殊霞,這時(shí)候把next重置于0摧阅,總不能把位置0的前移吧,所以這里需要判斷是不是等于下一個(gè)可存放位置绷蹲,如果等于棒卷,然后直接把 i位置的置于0,然后把 下一個(gè)可存的下標(biāo)設(shè)置為i祝钢。等等比规? ?這里是不是正好解決了我們前面哪個(gè)問(wèn)題? 如果我刪除了中間一個(gè)拦英,則會(huì)把下一個(gè)可存放重置到最后一個(gè)位置蜒什。


這里需要注意的是,能夠讓下一個(gè)可取下標(biāo)變化的操作一定是從數(shù)組中刪除了這個(gè)元素的操作龄章,這樣也就避免了我們前面提到的問(wèn)題吃谣,即不存在覆蓋的問(wèn)題乞封。

removeAll是在AbstractCollection抽象類(lèi)里實(shí)現(xiàn)的做裙,通過(guò)迭代器操作。


還有最后一個(gè)removeIf


也是通過(guò)迭代器操作肃晚,然后多了一個(gè)判斷條件锚贱,也就是條件過(guò)濾。這個(gè)predicate僅僅是個(gè)接口关串,目前還沒(méi)有任何實(shí)現(xiàn)拧廊,可以通過(guò)自己來(lái)定義實(shí)現(xiàn),需要重寫(xiě)evaluate方法晋修。有興趣的可以自己查看吧碾。

下面再看看我們別的API的實(shí)現(xiàn)。


element方法返回下一個(gè)可獲取的對(duì)象墓卦,如果為Null拋出異常


contain(1);返回是否包含這個(gè)對(duì)象倦春,


arr.drainTo(l); 這個(gè)方法參數(shù)是一個(gè)Collection對(duì)象,可以吧隊(duì)列中的數(shù)據(jù)批量復(fù)制到這個(gè)集合中去落剪,


第二個(gè)參數(shù)是復(fù)制多少個(gè)睁本,如果不設(shè)置默認(rèn)復(fù)制全部,然后刪除掉queue中對(duì)應(yīng)的對(duì)象忠怖。這個(gè)有沒(méi)有一點(diǎn) 批量獲取的意思呢呢堰?


接下圖


接上圖。

如果指定第二個(gè)參數(shù)凡泣,則會(huì)從可獲取下標(biāo)開(kāi)始然后復(fù)制指定個(gè)數(shù)的對(duì)象枉疼。

這里如果我們數(shù)組容量為5皮假,批量拉取了前3個(gè),然后后面的會(huì)不會(huì)前移呢骂维?從源碼中看钞翔,不會(huì),而且沒(méi)有必要席舍。我們的下一個(gè)存下標(biāo)和下一個(gè)取下標(biāo)會(huì)不停的循環(huán)布轿,所以隊(duì)列即使形成了中空,都是沒(méi)有關(guān)系的来颤。之前有一個(gè)ringbuffer的設(shè)計(jì)汰扭,大家可以百度搜的看一看,而且disruptor的ringbuffer設(shè)計(jì)的更為巧妙福铅。

clear()清空所有


arr.size();返回實(shí)際存放的大小


remainingCapacity()返回剩余多少可用容量

arr.toArray();返回一個(gè)對(duì)象數(shù)組


arr.toArray(new Integer[10]);和上面的區(qū)別就是如果不指定則返回一個(gè)object[]數(shù)組萝毛,如果指定則返回到你指定的數(shù)組中。


arr.spliterator(); 這個(gè)是返回一個(gè)spliterator滑黔,我還沒(méi)有學(xué)習(xí)過(guò)這個(gè)笆包,所以這里暫時(shí)不介紹。

因?yàn)槭腔跀?shù)組實(shí)現(xiàn)的略荡,所以這個(gè)的優(yōu)點(diǎn)不必說(shuō)庵佣,存入和取出都非常快汛兜,唯一慢的一個(gè)地方就是指定刪除巴粪。如果數(shù)組特別大,比如有1000個(gè)容量粥谬,然后我們要?jiǎng)h除第2個(gè)對(duì)象肛根,然后就需要把后面的全部前移一位。所以對(duì)于有這種需要的需要慎用漏策。

還有一點(diǎn)就是不能擴(kuò)容派哲,因此初始化時(shí)候必須要根據(jù)業(yè)務(wù)初始化為合適大小的隊(duì)列。

使用場(chǎng)景不必說(shuō):

對(duì)于快速存快速取掺喻,不存在刪除中間的需求來(lái)說(shuō)是非常合適的芭届。


如又錯(cuò)誤,以及意見(jiàn)建議巢寡,歡迎有緣看帖者能夠給出批評(píng)指正喉脖,不甚感激。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抑月,一起剝皮案震驚了整個(gè)濱河市树叽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谦絮,老刑警劉巖题诵,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洁仗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡性锭,警方通過(guò)查閱死者的電腦和手機(jī)赠潦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)草冈,“玉大人她奥,你說(shuō)我怎么就攤上這事≡趵猓” “怎么了哩俭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)拳恋。 經(jīng)常有香客問(wèn)我凡资,道長(zhǎng),這世上最難降的妖魔是什么谬运? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任隙赁,我火速辦了婚禮,結(jié)果婚禮上梆暖,老公的妹妹穿的比我還像新娘伞访。我一直安慰自己,他們只是感情好式廷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布咐扭。 她就那樣靜靜地躺著,像睡著了一般滑废。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袜爪,一...
    開(kāi)封第一講書(shū)人閱讀 50,043評(píng)論 1 291
  • 那天蠕趁,我揣著相機(jī)與錄音,去河邊找鬼辛馆。 笑死俺陋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昙篙。 我是一名探鬼主播腊状,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼苔可!你這毒婦竟也來(lái)了缴挖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤焚辅,失蹤者是張志新(化名)和其女友劉穎映屋,沒(méi)想到半個(gè)月后苟鸯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棚点,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年早处,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘫析。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砌梆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贬循,到底是詐尸還是另有隱情么库,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布甘有,位于F島的核電站诉儒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亏掀。R本人自食惡果不足惜忱反,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滤愕。 院中可真熱鬧温算,春花似錦、人聲如沸间影。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)魂贬。三九已至巩割,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間付燥,已是汗流浹背宣谈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留键科,地道東北人闻丑。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像勋颖,于是被迫代替她去往敵國(guó)和親嗦嗡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等饭玲,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,490評(píng)論 0 3
  • 第5章 引用類(lèi)型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類(lèi)型 使用基本類(lèi)型...
    大學(xué)一百閱讀 3,219評(píng)論 0 4
  • ArrayBlockingQueue是一個(gè)有界的阻塞隊(duì)列侥祭,所有的元素按照先入先出(FIFO)的規(guī)則進(jìn)隊(duì)出隊(duì),在隊(duì)列...
    辣公公閱讀 389評(píng)論 0 0
  • 考察了一下自稱(chēng)為“most popular”前端framework:bootstrap。初步認(rèn)為,并不適用于我們的...
    NoteCode閱讀 377評(píng)論 1 1
  • 如果世界上有種藥吃了以后可以回到從前某一刻,讓你對(duì)某件事情重新做一次選擇欢伏,你吃不吃入挣? 我猜很多人傾家蕩產(chǎn)...
    星暖KK閱讀 630評(píng)論 2 7