雖然在某程序員最喜歡的工具調(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)系圖。
其實(shí)這個(gè)類(lèi)主要的方法是在BlockingQuere接口中定義好的牧挣。所有的實(shí)現(xiàn)除了add方法調(diào)用了abstarctQueue之外急前,其他都是自己實(shí)現(xiàn)的。下面我們來(lái)看看他是怎么實(shí)現(xiàn)的瀑构。
第一個(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è)需要指定初始化大小谱俭,這里的數(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)指正喉脖,不甚感激。