并發(fā)包下的linkedBlockingQueue和ConcurrentLinkedQueue的區(qū)別是什么?我們先帶著這個(gè)問(wèn)題慢慢讀源碼:先來(lái)看看類層次結(jié)構(gòu),基本上和ConcurrentLinkedQueue差不多,唯一不同的是后者是非阻塞的。

第一步:從全局屬性推測(cè)功能
第一個(gè)capacity是一個(gè)容量,也就是可以設(shè)置我們阻塞隊(duì)列鏈條的長(zhǎng)度,第二個(gè)是一個(gè)原子的計(jì)數(shù)器,從英文解釋來(lái)看用來(lái)統(tǒng)計(jì)元素個(gè)數(shù),第三個(gè)是頭節(jié)點(diǎn),第四個(gè)指向尾節(jié)點(diǎn),然后這里維護(hù)了2把鎖,獲取鎖,非空condition,添加鎖,非滿condition。
通過(guò)第二張圖的Node對(duì)象的屬性可以知道,這個(gè)隊(duì)列是單向隊(duì)列,其實(shí)并發(fā)包下對(duì)linked隊(duì)列都同時(shí)提供了雙向隊(duì)列,LinkedBlockingDeque、ConcurrentLinkedDeque。同時(shí)對(duì)阻塞的鏈表隊(duì)列還提供了一個(gè)LinkedTransferQueue。這個(gè)具體功能再開(kāi)一個(gè)帖子來(lái)寫(xiě)。


第二步:構(gòu)造函數(shù)


? ? ? 從上圖可以看出,默認(rèn)情況下我們的隊(duì)列容量是最大值,也可以通過(guò)帶參數(shù)的來(lái)進(jìn)行設(shè)置,然后同時(shí)初始化一個(gè)null節(jié)點(diǎn),并把頭和尾都指向這個(gè)節(jié)點(diǎn)。
第三步:添加方法,也同時(shí)提供了4個(gè)方法
add 成功返回true, 失敗拋出異常
put成功返回true,不然阻塞直到成功
offer提供了2個(gè)重載,一個(gè)是成功true,失敗返回false,還有一個(gè)可以設(shè)置阻塞時(shí)間,阻塞時(shí)間內(nèi)成功true,失敗返回false。

先來(lái)看add方法,add方法調(diào)用的是abstractQuere的add方法,和ArrayBlockingQueue是一樣的,也是通過(guò)調(diào)用offer方法,如果返回false拋出異常,或者成功。

offer方法源碼如下圖,通過(guò)原子計(jì)數(shù)器進(jìn)行容量統(tǒng)計(jì),如果和容量一樣,則返回false,如果不等于則把元素包裝成node,然后在鎖內(nèi)通過(guò)enqueue方法完成添加操作,最后釋放鎖,同時(shí)對(duì)追加后的容量判斷,如果不滿則調(diào)用非滿喚醒,如果元素個(gè)數(shù)為0則調(diào)用非空喚醒。

在添加的方法里很簡(jiǎn)單,就是把last,和last.next都指向新加入的節(jié)點(diǎn)

下面是帶阻塞時(shí)間的offer方法,首先如果滿了的情況下,調(diào)用帶時(shí)間參數(shù)的阻塞方法阻塞,然后阻塞時(shí)間結(jié)束后,如果有空位置,則加入,如果沒(méi)有則直接返回false。同樣需要對(duì)非滿進(jìn)行喚醒,這里的c默認(rèn)開(kāi)始是-1,當(dāng)用c=count.getAndIncrement()方法后,相當(dāng)于對(duì)c進(jìn)行了+1操作,然后為0,代表此時(shí)添加一條數(shù)據(jù)成功,所以c變?yōu)?,然后進(jìn)行非空喚醒。

下面是Put方法,這里如果滿的時(shí)候就會(huì)一致阻塞,直到其他取出操作喚醒。

下面來(lái)看取出和刪除的各種方法
take方法會(huì)阻塞到能取出一個(gè)為止,從head取出,同時(shí)從鏈條中刪除
poll方法會(huì)從head開(kāi)始取出一個(gè),同時(shí)從鏈條中刪除,還有一個(gè)可以設(shè)定阻塞時(shí)間的重載方法,可以設(shè)定一定時(shí)間內(nèi)娶不到則返回。
peek方法直接返回當(dāng)前head,但是不會(huì)刪除
remove方法,從head移除一個(gè),如果沒(méi)有則拋出異常
renmove還可以指定移除一個(gè)。如果沒(méi)有則拋出異常

下面是tabke方法,通過(guò)取鎖,先獲得鎖,然后如果鏈條為空則阻塞,直到存入操作喚醒這個(gè)阻塞,然后用dequeue方法獲取一個(gè),同時(shí)對(duì)統(tǒng)計(jì)數(shù)減一操作,然后做其他喚醒操作。

dequeue方法也是一樣的,首先拿到head節(jié)點(diǎn),然后把head指向下一個(gè)節(jié)點(diǎn),同時(shí)把取出來(lái)的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)指向自己,這樣關(guān)閉了鏈條,可以幫助GC回收,然后取出更新后頭節(jié)點(diǎn)的對(duì)象,然后把頭結(jié)點(diǎn)的item設(shè)置為Null,由此可以看到頭節(jié)點(diǎn)的item一直是Null,然后next指向的才是有item對(duì)象的節(jié)點(diǎn)。也就是頭結(jié)點(diǎn)其實(shí)是個(gè)Null。

poll方法則是如果成功返回對(duì)象,失敗返回null,由下圖源碼可以看到,如果鏈條為空直接返回null,如果不為空,利用鎖直接去到第一個(gè)節(jié)點(diǎn)返回。

peek方法是僅僅返回第一個(gè)節(jié)點(diǎn)的item,并不從鏈條里移除。

帶有阻塞時(shí)間的poll方法會(huì)根據(jù)設(shè)定時(shí)間阻塞,超時(shí)后有則返回對(duì)象,沒(méi)有則返回null。

remove方法直接調(diào)用的poll,如果返回Null則拋出異常。

指定移除則是從頭開(kāi)始遍歷節(jié)點(diǎn),直到發(fā)現(xiàn)為止,然后直接把上一個(gè)節(jié)點(diǎn)的next指向當(dāng)前滿足條件的next,然后將滿足條件的節(jié)點(diǎn)的item設(shè)置為null。


但是我們發(fā)現(xiàn),p.next對(duì)下一個(gè)的引用還沒(méi)有解除,這里不知道為什么不解除?

下面我們看看其他一些API

size方法返回當(dāng)前鏈條中的節(jié)點(diǎn)個(gè)數(shù)

clear方法則是一個(gè)把取和存的鎖都鎖上,然后遍歷刪除,最后解鎖

element直接返回第一個(gè)節(jié)點(diǎn),同時(shí)不進(jìn)行刪除

判斷是不是空也直接用我們的原子計(jì)數(shù)器

查詢鏈條中是否包含某個(gè)對(duì)象,也是同時(shí)上2把鎖。

基本上常用的方法我們已經(jīng)讀完了。這里我們做一個(gè)總結(jié)
鏈?zhǔn)疥?duì)列采用的是2把鎖,一把寫(xiě)鎖,一把取鎖。這樣就能實(shí)現(xiàn)取取互斥,寫(xiě)寫(xiě)互斥,寫(xiě)取不互斥。
因?yàn)槿】偸菑念^部開(kāi)始,而寫(xiě)總是從尾部開(kāi)始,這種情況下對(duì)于并發(fā)不同操作能夠提供更好的支持,這里要對(duì)比數(shù)組隊(duì)列為什么是一把鎖呢? 數(shù)組隊(duì)列其實(shí)是一個(gè)環(huán)形實(shí)現(xiàn),操作也是同步從頭開(kāi)始,然后環(huán)形回繞。

從鎖性能上來(lái)說(shuō),肯定是鏈條的更優(yōu)化,但是我們知道數(shù)組底層在內(nèi)存中是連續(xù)的,一次讀入一個(gè)緩存行,一個(gè)緩存行存有多個(gè)數(shù)據(jù),這樣能減少CPU與內(nèi)存的交互,
但是鏈條在內(nèi)存中不一定是連續(xù)的,每次取下一個(gè)位置都需要去內(nèi)存中讀取。CPU和內(nèi)存會(huì)頻繁交互
二者都是有界的,可以根據(jù)需求不同定性選擇。