ArrayBlockingQueue源碼解讀

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

好了廢話不多說,開搞。今天讀的源碼的是ArrayBlockingQueue。首先來看一張類接口關(guān)系圖。


層次關(guān)系圖

其實這個類主要的方法是在BlockingQuere接口中定義好的。所有的實現(xiàn)除了add方法調(diào)用了abstarctQueue之外,其他都是自己實現(xiàn)的。下面我們來看看他是怎么實現(xiàn)的。


類的全局變量


第一個final的object數(shù)組即是實現(xiàn)的容器,這里我們要強調(diào)一下,如果你了解類的加載過程,那你就對這個一點也不奇怪了,如果不懂你可以去看一下類的加載過程,這里的final沒有被static修飾,也就代表著它的初始化是在new對象的Init方法里初始化的。init也就是我們的構(gòu)造函數(shù)執(zhí)行的過程。

takeindex代表著下一個可取的數(shù)組下標(biāo)。初始化為0,然后調(diào)用poll,remove,take方法都會進行++操作,如果>=數(shù)組容量長度,則重置為0

putindex代表著下一個可存放的數(shù)組下標(biāo),初始化為0,如果調(diào)用add,offer,put則進行++操作,如果>=數(shù)組容量長度,則重置為0

count代表總共存放了多少個對象,

下面維護了一把鎖,2個condition,notempty用于維護的是空阻塞,這里比如調(diào)用poll和take的時候如果count為0則會進行阻塞,只要添加一個,則會執(zhí)行notempty.signl操作喚醒阻塞,具體的情況下會講解。notFull維護的是滿阻塞,調(diào)用offer和put如果數(shù)組滿了則會阻塞。

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

當(dāng)然這里重點講解的是重點幾個API實現(xiàn)的過程。下面我們一個一個的展開講。

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


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

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


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


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


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

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

首先看add、put、offer方法。首先說下三個方法的區(qū)別:

add,添加失敗會拋出異常

put,如果數(shù)組滿則會阻塞到有空位為止

offer,添加失敗會返回false。

下面先來看add方法。


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


從這里看為什么我們add方法會拋出異常,原來就是這么來的,如果添加失敗則拋出異常。添加動作能交給offer去做,添加失敗返回false,然后就拋出異常,其實就是對offer的返回形式進行了一次改變而已嘛!

下面我們看offer操作

offer提供了2個操作,一個是只有一個待添加元素的參數(shù),另外一個是多了2個參數(shù),一個是long類型基本數(shù)據(jù)類型,一個是jdk1.8的新的時間對象工具。不要著急,我們一個個來看。



先來看1個參數(shù)的:


一進來首先對它進行null值檢測,其實實現(xiàn)很簡單,就是如果為null拋出空指針異常。

然后鎖定,判斷,最終添加操作是在enqueue這個操作里實現(xiàn)的。其實三種添加操作都是通過enqueue方法實現(xiàn)的,所有的取出操作都是dequeue方法實現(xiàn)的。然后如果成功返回true,如果失敗返回false.就是這么實現(xiàn)的。


添加操作。

這里的實現(xiàn)很簡單,直接放入下一個可存放的位置,然后對下一個可存放下標(biāo)++操作,然后與數(shù)組容量判斷,如果滿了則置為0,然后對記錄總存量的count進行++操作。然后調(diào)用非空鎖喚醒操作。這里留一個疑問,比如我當(dāng)前添加滿了,然后下一個可存放的位置重置為0,這時候另外一個操作按照下標(biāo)取走了中間一個位置的對象,或者是末尾位置的對象,這時候數(shù)組是有空位置的,這時候我們再存入一個時候會發(fā)生什么事情呢

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


第一個參數(shù)是待存入元素,第二個參數(shù)是超時時間,第三個參數(shù)是時間單位。

通過上面的while循環(huán)可知,如果阻塞超時,則直接返回false,如果阻塞期間,count變化了,則進入下面的enqueue方法存入對象。實現(xiàn)就這么簡單。

還剩最后一個我們看put方法,


put方法就是在while循環(huán)里阻塞,然后等待喚醒。這里的lock鎖是通過lockInterruptibly();進行獲取的,這里我寫這的時候也不明白為什么這么寫。后面會研究下關(guān)于鎖的獲取的不同方式。

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

下面我們看取的幾個實現(xiàn),包括take,poll,peek。

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

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

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

先來看take的實現(xiàn)方式



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


這里先把下一個可獲取元素位置的下標(biāo)取出來,然后把這個位置修改為null,然后在進行對可獲取下標(biāo)位置的修改,如果==數(shù)組容量則重置為0.然后對非滿進行喚醒操作。


poll方法有2個實現(xiàn),一個是如果為空立即返回null,另外一個就是阻塞等待時間,如果超時立即返回。


用了一個三目運算符,soeasy


這里也會在while循環(huán)里進行阻塞,如果超時返回null,如果有新的對象進來,則調(diào)用dequeue。

下面看peek方法


peek直接返回了下一個可取位置的對象,不進行刪除操作。

到此為止,我們的添加和獲取都看過了,下面來看remove方法,有4個方法

remove(o),刪除指定對象

remove()刪除一個

removeAll刪除集合中的全部對象

removeIf()


先來看不帶參數(shù)的,remove方法直接返回了當(dāng)前移除的對象,刪除的呢正好是下一個可獲取的對象。


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

下面看刪除指定對象


這里首先我們找出需要刪除對象的下標(biāo),通過指定下標(biāo)取刪除

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

如果指定刪除的下標(biāo)正好等于可獲取的下一個下標(biāo),則直接把該位置的修改為null,然后對下一個可獲取的下標(biāo)進行++操作,如果等于數(shù)組長度,則重置為0;


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


這里我們假設(shè)目前5個大小的數(shù)組是滿的,下一個可存放下標(biāo)重置為0了,然后待移除的下標(biāo) i 位置為2,則next=2+1,然后把下標(biāo)為3的移動到下標(biāo)為2的,然后把 i=next,循環(huán),然后當(dāng)i=4的時候,我們的next=5與數(shù)組容量一樣大小了,這時候把next重置于0,總不能把位置0的前移吧,所以這里需要判斷是不是等于下一個可存放位置,如果等于,然后直接把 i位置的置于0,然后把 下一個可存的下標(biāo)設(shè)置為i。等等? ?這里是不是正好解決了我們前面哪個問題? 如果我刪除了中間一個,則會把下一個可存放重置到最后一個位置。


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

removeAll是在AbstractCollection抽象類里實現(xiàn)的,通過迭代器操作。


還有最后一個removeIf


也是通過迭代器操作,然后多了一個判斷條件,也就是條件過濾。這個predicate僅僅是個接口,目前還沒有任何實現(xiàn),可以通過自己來定義實現(xiàn),需要重寫evaluate方法。有興趣的可以自己查看。

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


element方法返回下一個可獲取的對象,如果為Null拋出異常


contain(1);返回是否包含這個對象,


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


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


接下圖


接上圖。

如果指定第二個參數(shù),則會從可獲取下標(biāo)開始然后復(fù)制指定個數(shù)的對象。

這里如果我們數(shù)組容量為5,批量拉取了前3個,然后后面的會不會前移呢?從源碼中看,不會,而且沒有必要。我們的下一個存下標(biāo)和下一個取下標(biāo)會不停的循環(huán),所以隊列即使形成了中空,都是沒有關(guān)系的。之前有一個ringbuffer的設(shè)計,大家可以百度搜的看一看,而且disruptor的ringbuffer設(shè)計的更為巧妙。

clear()清空所有


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


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

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


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


arr.spliterator(); 這個是返回一個spliterator,我還沒有學(xué)習(xí)過這個,所以這里暫時不介紹。

因為是基于數(shù)組實現(xiàn)的,所以這個的優(yōu)點不必說,存入和取出都非??欤ㄒ宦囊粋€地方就是指定刪除。如果數(shù)組特別大,比如有1000個容量,然后我們要刪除第2個對象,然后就需要把后面的全部前移一位。所以對于有這種需要的需要慎用。

還有一點就是不能擴容,因此初始化時候必須要根據(jù)業(yè)務(wù)初始化為合適大小的隊列。

使用場景不必說:

對于快速存快速取,不存在刪除中間的需求來說是非常合適的。


如又錯誤,以及意見建議,歡迎有緣看帖者能夠給出批評指正,不甚感激。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容