ConcurrentLinkedQueue源碼解讀

上一篇介紹了ArrayBlockingQueue的源碼,這節(jié)我們介紹它的兄弟,基于鏈表的實(shí)現(xiàn),直接開看

head見名知意:這里必須要說的是一個好的名字,真的能夠給閱讀者帶來不一樣的便利。我讀過的源碼,是深有體會的。也就是我們鏈表的頭

tail同樣我們可以知道是尾巴節(jié)點(diǎn),二者都是一個Node類型,我們看看這個類的定義


只有2個屬性,一個是泛型,一個是指向下一個節(jié)點(diǎn)的next。

下面是構(gòu)造函數(shù),一個帶集合對象參數(shù)的構(gòu)造函數(shù),一個無參構(gòu)造函數(shù)

如果是無參的直接把頭尾都初始化為null,

如果是有參數(shù)的,則如下圖,遍歷我們傳進(jìn)來的集合,然后取出每一個對象,并且包裝為node對象,然后遍歷并且調(diào)用node的lazySetNext執(zhí)行鏈條指針設(shè)置,通過名字可以理解為是一個延遲設(shè)置,并且把t永遠(yuǎn)指向最新加入的一個。最后然后把head和tail指向了h和t。

我們下面看看lazySetNext的方法實(shí)現(xiàn)

這里直接調(diào)用了UNSAFE對象的putorderedObject方法,三個參數(shù)分別是當(dāng)前node,偏移量,下一個node對象。從下圖中可知,node在初始化的時候根據(jù)node類的類對象通過反射獲取了next這個屬性,我們node類的next屬性就是指向的下一個node,然后通過UNSAFE的objectFieldOffset設(shè)置為偏移量。這里可以理解為,每次我創(chuàng)建node對象的時候,通過反射機(jī)制,返回一個新的node,然后設(shè)置為屬性偏移量。有興趣的同學(xué)可以去讀下反射的這些源碼,反射返回的是一個復(fù)制的新對象。也就是說我們每次初始化的時候已經(jīng)對node里面的nextNode通過反射也進(jìn)行了初始化。

這里不知道大家注意到?jīng)],我們并沒有設(shè)置邊界,也就是說很可能鏈表隊(duì)列是一個無邊界的。

這里提出個問題:隊(duì)列的大小怎么維護(hù)的,目前為止我們還沒有看到過隊(duì)列大小的屬性值。

下面我們看add方法,

通過解讀可以知道,是沒有邊界的,所以add方法永遠(yuǎn)會成功

這里調(diào)用了offer方法,我們來看看offer實(shí)現(xiàn)

這里假設(shè)我們第一次添加一個節(jié)點(diǎn)1,然后此時tail為null,然后p.next為null,然后用cas改變head為節(jié)點(diǎn)1,tail的next改變?yōu)橹赶蜃约?/p>

接下來我們添加第二個節(jié)點(diǎn)2,此時tail為null,t為null,p為null,p.next為自身,顯然q不為null,然后q==p,然后通過三目運(yùn)算符把p指向了節(jié)點(diǎn)1.

過程如下圖:


后面的繼續(xù)追加基本就是這個模式了,這里用了個for循環(huán),在并發(fā)下保證能追加進(jìn)去。

這里有一個問題我也搞不清楚,從后面的節(jié)點(diǎn)debug查看,CAS算法的確是把next指向了新入的節(jié)點(diǎn),但是當(dāng)?shù)谝粋€節(jié)點(diǎn)null的時候,用CAS產(chǎn)生的效果是,head指向了新加入節(jié)點(diǎn),tail的next指向了自身。我一直也沒搞明白,然后在加入節(jié)點(diǎn)后把這個Null的節(jié)點(diǎn)丟棄掉。至此,無鎖的linkedqueue的追加就完成了。

下面我們來看poll方法,poll方法總是彈出第一個,也就是先進(jìn)先出。從這里可以看得出在并發(fā)下,如果產(chǎn)生競爭,也是通過不停的修改head的for循環(huán)來進(jìn)行彈出的。這里p.casitem后同時會把下一個節(jié)點(diǎn)升級為head。這里如果多線程環(huán)境下,當(dāng)一個線程拿到了某一個節(jié)點(diǎn)a,第二個線程也拿到了a,但是搶先去拿走了值,此時a節(jié)點(diǎn)的下一個節(jié)點(diǎn)為null,這時候head也為Null,然后p.next為null,h為null,所以直接返回了null值。如果P.next指向了自身,則滿足第三個判斷,然后這時候跳過本次循環(huán)從最上面開始再次進(jìn)入循環(huán)。如果q=p.next不為null,p也不和q相等,則直接把p指向next,然后再次循環(huán)。這樣就基本解決了所有的可能性:

? ? ? ? ? ? ?需要彈出的p節(jié)點(diǎn)不為Null

? ? ? ? ? ? ?需要彈出的p節(jié)點(diǎn)為null,但是下個節(jié)點(diǎn)不為null,但指向自己

? ? ? ? ? ? ?需要彈出的p節(jié)點(diǎn)為null,但是下個節(jié)點(diǎn)不為null,但也不指向自己

? ? ? ? ? ? ?需要彈出的節(jié)點(diǎn)為null,下一個也為null,然后返回null。

peek操作總返回head,但是不從鏈接上刪除,這里的判斷條件為,當(dāng)前節(jié)點(diǎn)的item如果不為null,則直接對自身進(jìn)行一個uphead,如果當(dāng)前節(jié)點(diǎn)的item為null,則繼續(xù)比價當(dāng)前節(jié)點(diǎn)的下一個節(jié)點(diǎn)是不是null,如果是null,則進(jìn)行一個偽head替換,然后返回Null,如果不是則比較當(dāng)前節(jié)點(diǎn)與當(dāng)前節(jié)點(diǎn)的下一個節(jié)點(diǎn)是不是相等,如果相等則跳過本次循環(huán)繼續(xù)從頭開始,如果不相等則把當(dāng)前節(jié)點(diǎn)指向下個節(jié)點(diǎn),然后再次循環(huán)。

isempty方法是擁來查看是否為空

調(diào)用的是first,方法,這里面進(jìn)行了比較判斷,也是基于兩個for嵌套循環(huán),這個給我們對于并發(fā)下實(shí)現(xiàn)無鎖模型算法提供了非常好的思路。如果當(dāng)前節(jié)點(diǎn)的item不為null,進(jìn)行一個偽head替換,然后返回當(dāng)前節(jié)點(diǎn),如果為null,則查看下一個節(jié)點(diǎn)是不是null,如果是null,則進(jìn)行一個偽升級,然后返回null,如果下一個節(jié)點(diǎn)不為null,則考慮是不是指向自身,如果是則重新開始循環(huán),如果不是,則把當(dāng)前節(jié)點(diǎn)指向下一個然后繼續(xù)循環(huán)。

size方法,前面我們注意到了,在這個queue中并未維護(hù)關(guān)于大小的數(shù)字,我們接下來看size怎么實(shí)現(xiàn),這里是在size方法里面進(jìn)行統(tǒng)計的,在原文注釋中解釋這個返回值并不準(zhǔn)確,并發(fā)條件下的追加和刪除都可能使得返回的size不正確。

contains(o)方法返回是否包含此對象,由方法可以看出,通過遍歷這個鏈條來比較,直到當(dāng)前的p為null為止。并發(fā)條件下僅僅可以作為判斷是否曾經(jīng)存在過,也許在返回true的那一刻,就被消費(fèi)掉了。這種狀態(tài)僅僅保證存在過,不保證下一刻存在。

remove方法需要指定刪除,也是需要遍歷整個鏈條,如果找到了則立馬把當(dāng)前的item修改為null,然后獲取到下一個節(jié)點(diǎn),然后用前一個指向下一個完成刪除。這里的刪除分2步,第一步首先把item修改為Null,第二步然后修改前一個鏈條和下一個鏈條的關(guān)系完成刪除。

toArray返回一個Object數(shù)組,但是對鏈條中的節(jié)點(diǎn)不進(jìn)行刪除操作

toArray數(shù)組返回到奧指定的數(shù)組中,從源碼中可以看到,能返回指定的數(shù)組

addAll方法可以直接批量添加一個集合,首先對集合遍歷,然后把每一個對象包裝為node,然后先將這個集合進(jìn)行自身鏈條化,而不是一個個先追加到原來的鏈條上,這樣既能保證我們集合的連續(xù)性,同時也避免了并發(fā)條件下帶來的復(fù)雜性。

當(dāng)上面的鏈條完成后,我們開始往我們真正的鏈條上追加,可以看到如果tail的next為null,則直接把我們前面鏈條的第一個然后連接到next即可,然后把tail指向最后一個,如果失敗后先把t指向tail然后把最后一個的next指向null,再進(jìn)行一次tail的改變。


至此我們整個源碼都結(jié)束了,由此引出來的2個問題

? ? ? ? ?第一:如果CAS時,需要修改的對象為null的node,則我們會把目標(biāo)對象變?yōu)槲覀兊膆ead,然后把null的node的next指向自身

? ? ? ? 第二:在我們poll的時候,也可以看出這一點(diǎn)來,如果當(dāng)前節(jié)點(diǎn)為null,則直接把目標(biāo)對象變?yōu)槲覀兊膆ead.這里不知道是CAS的算法,還是java自身對CAS的進(jìn)一步優(yōu)化呢?

整個LinkedBlockQueue是無鎖的,全部都基于for循環(huán)和CAS算法,而且也是無界的。

關(guān)于這個的優(yōu)勢就是真的快,無鎖并發(fā)下,通過不停的循環(huán)來追加,會很快,因此也會比較耗內(nèi)存和CPU,因?yàn)槿绻粚φ埱缶€程限制,同一時間會有很多線程在不停的搶cpu資源,他們不存在線程終結(jié)的概念,只有線程執(zhí)行完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評論 2 375

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