Jdk1.6和1.7版本中ConcurrentHashMap的弱一致性

前言:
昨天看技術(shù)論壇時(shí)看到了一篇<<為什么ConcurrentHashMap是弱一致的>>文章,其中的弱一致性是基于Jdk1.6版本的ConcurrentHashMap源碼分析的索昂,而在Jdk1.7和Jdk1.8中ConcurrentHashMap的設(shè)計(jì)都進(jìn)行了一定程度的改良沼溜,是否仍然存在1.6版本中的弱一致性呢耸袜?
本文為了分析方便混驰,假設(shè)有兩個(gè)線程對(duì)一個(gè)ConcurrentHashMap實(shí)例進(jìn)行并發(fā)處理,采用put-get這一并發(fā)操作組合來(lái)分析數(shù)據(jù)的一致性刻炒,put-get組合表示一個(gè)線程執(zhí)行put方法時(shí)另一個(gè)線程執(zhí)行g(shù)et方法决采。另外,文章從1.6版本和1.7版本兩個(gè)維度同時(shí)分析同一組合操作時(shí)的現(xiàn)象坟奥,進(jìn)一步驗(yàn)證結(jié)論树瞭。
閱讀此文章讀者可能需要Java內(nèi)存模型(JMM)、volatile內(nèi)存語(yǔ)義晒喷、Happens Before關(guān)系、CAS访敌、AQS凉敲、ConcurrentHashMap不同版本實(shí)現(xiàn)原理等基礎(chǔ)前驅(qū)知識(shí),文章只對(duì)一致性問題進(jìn)行分析

在分析之前首先要知道ConcurrentHashMap在1.6和1.7中用volatile修飾的變量有哪些,如下表所示

1.6 1.7
Segment.count Segment.HashEntry[]
Segment.HashEntry[] Segment.HashEntry.value
Segment.HashEntry.value Segment.HashEntry.next

眾所周知爷抓,ConcurrentHashMap使用鎖分離技術(shù)势决,初始時(shí)有16個(gè)Segment段組成,一個(gè)Segment段包含一個(gè)類型為HashEntry的數(shù)組table蓝撇,一個(gè)HashEntry元素又存在key-value的鍵值對(duì)果复,以及指向下一個(gè)HashEntry元素的指針next
所以,表中的Segment.count表示每個(gè)Segment段中HashEntry[]內(nèi)元素的數(shù)量渤昌;Segment.HashEntry[]表示Segment段內(nèi)的HashEntry[]數(shù)組虽抄;Segment.HashEntry.value表示一個(gè)HashEntry元素中的value值;Segment.HashEntry.next表示HashEntry鏈表中下一個(gè)元素

圖1. 1.6版本put方法
圖2. 1.6版本get方法

因?yàn)?code>count被volatile修飾独柑,因此標(biāo)注1是對(duì)volatile的讀操作迈窟,同理標(biāo)注2也是對(duì)volatile修飾的table(HashEntry[])的讀操作;根據(jù)Happens Before規(guī)則中的程序順序規(guī)則(一個(gè)線程中的每個(gè)操作群嗤,happens-before于該線程中的任意后續(xù)操作)菠隆,可以看出 1 happens before 2 happens before 3 happens before 4。其中狂秘,標(biāo)注3是對(duì)普通變量的普通寫操作骇径,標(biāo)注4是對(duì)volatile變量count的寫操作;同樣的道理在圖2中存在5 happens before 6
那么我們模擬一下兩個(gè)線程分別操作put方法和get方法的情況者春,首先是“正称葡危”的情況

圖3. 1.6版本下數(shù)據(jù)一致性正常的情況

圖中因?yàn)闃?biāo)注1、3钱烟、4在同一線程中晰筛,因此存在happens before關(guān)系,另一個(gè)線程的5和6也存在happens before關(guān)系拴袭;此外读第,根據(jù)Happens Before規(guī)則中的volatile變量規(guī)則(對(duì)一個(gè)volatile域的寫,happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀)可以推理出4 happens before 5拥刻,因?yàn)?是對(duì)volatile變量count的寫操作怜瞒,5是對(duì)count的讀操作。在根據(jù)Happens Before規(guī)則中的傳遞性(如果A happens-before B般哼,且B happens-before C吴汪,那么A happens-before C),最終推出1 happens before 2 happens before 3 happens before 4 happens before 5 happens before 6蒸眠,因此此時(shí)數(shù)據(jù)的一致性是得到保證的漾橙。那么我們?cè)賮?lái)看看弱一致的情況

圖4. 1.6版本下數(shù)據(jù)弱一致性的情況

我們更改了4、5執(zhí)行的順序楞卡,一個(gè)線程先執(zhí)行對(duì)volatile變量count的讀操作霜运,之后另一個(gè)線程再執(zhí)行對(duì)count的寫操作脾歇,這樣4、5之間就不存在happens before關(guān)系了觉渴,并且上面分析過3的操作只是普通變量的讀寫操作介劫,而5是對(duì)volatile變量table的讀操作徽惋,因此3案淋、5之間也不存在happens before關(guān)系,6中讀取的并不是3處添加新HashEntry的最新table险绘,這就導(dǎo)致了數(shù)據(jù)的弱一致性
下面我們來(lái)看看在Jdk1.7中ConcurrentHashMap的put和get方法

圖5. 1.7版本put方法
圖6. 1.7版本get方法

因?yàn)閠able為volatile修飾踢京,因此標(biāo)注1是一個(gè)volatile讀,用一個(gè)局部非volatile修飾引用了volatile修飾的volatile宦棺,這里必須要注意一個(gè)知識(shí)點(diǎn):volatile修飾的是reference瓣距,不是對(duì)象的實(shí)例,也就是說(shuō)table指向了一個(gè)堆內(nèi)內(nèi)容為HashEntry[]內(nèi)容的空間代咸,這里的volatile修飾的是這個(gè)table在棧內(nèi)的引用蹈丸,不是棧內(nèi)地址指向的堆內(nèi)內(nèi)容,而HashEntry<K,V>[] tab = table相當(dāng)于又用了另一個(gè)變量tab指向了變量table指向的同一塊內(nèi)存地址呐芥,但是tab引用并沒有被volatile修飾逻杖,所以tab是不具有volatile語(yǔ)義的相關(guān)特性的
標(biāo)注2調(diào)用了HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i)方法,源碼如下:

圖7. entryAt方法

entryAt方法調(diào)用了UNSAFE類的getObjectVolatile思瘟,該方法是根據(jù)第二個(gè)參數(shù)的offset偏移量查找對(duì)應(yīng)第一個(gè)參數(shù)中的值荸百,該方法是支持volatile讀語(yǔ)義的,其底層是在entryAt方法插入一個(gè)LoadLoad和一個(gè)LoadStore內(nèi)存屏障防止CPU可能的指令重排序
接著再回去看圖5中的標(biāo)注3處滨攻,將新加入的HashEntry實(shí)例放在HashEntry[]特定的位置上够话,下面是其源碼

圖7 setEntryAt方法

同樣的setEntryAt方法內(nèi)部也調(diào)用了UNSAFE類的putOrderedObject方法,這里又存在一個(gè)坑光绕,很多文章在分析該方法時(shí)都說(shuō)其是一個(gè)具有volatile語(yǔ)義的方法女嘲,或者是否具有volatile語(yǔ)義依賴于第一個(gè)參數(shù)是否是volatile變量,但實(shí)際上putOrderedObject并不具有volatile語(yǔ)義诞帐,該方法的底層省去了volatile寫的StoreLoad內(nèi)存屏障欣尼,只添加了StoreStore內(nèi)存屏障,所以只能保證putOrderedObject方法之前的內(nèi)存可見性景埃,不能保證數(shù)據(jù)的一致性媒至,讀者可以參考JUC中Atomic class之lazySet的一點(diǎn)疑惑,對(duì)該問題分析的非常漂亮谷徙,源碼扒到了祖墳上
根據(jù)Happens Before規(guī)則中的程序順序規(guī)則可以得出1 happens before 2 happens before 3拒啰,同理推出圖6中4 happens before 5 happens before 6,但是對(duì)比圖5和圖6的代碼發(fā)現(xiàn)完慧,圖5中和圖6中只有table一個(gè)被volatile修飾的變量被共享谋旦,而且在put方法中table是volatile讀,get方法中table也是volatile讀,按照Happens Before規(guī)則中的volatile變量規(guī)則册着,必須存在另一個(gè)volatile的寫拴孤,在這里也就是對(duì)于table變量的寫,且寫要在讀之前才會(huì)行成Happens Before關(guān)系甲捏,很明顯也不滿足
我們?cè)賹?duì)比1.6版本中是如何完成“部分?jǐn)?shù)據(jù)一致性”的演熟,在1.6中count變量被volatile修飾了,因此該變量可以作為兩個(gè)線程發(fā)生volatile的媒介司顿,但在1.7版本中芒粹,count變量沒有被volatile修飾,因此也不存在依靠該變量發(fā)生Happens Before關(guān)系的可能性大溜。put方法和get方法中都存在對(duì)于局部變量tab的volatile操作化漆,但經(jīng)過逃逸性分析,這里的局部變量并不會(huì)逃逸到另一個(gè)線程中钦奋,所以也不會(huì)存在Happens Before語(yǔ)義
最后只剩標(biāo)注4中的UNSAFE.getObjectVolatile(segments, u)座云,上面分析過,雖然參數(shù)中的segments沒有被volatile修飾付材,但是getObjectVolatile會(huì)強(qiáng)制在變量讀取之后加上LoadLoadLoadStore內(nèi)存屏障行成volatile讀語(yǔ)義朦拖,但在put方法時(shí)也不存在對(duì)于該共享變量的volatile寫操作,也就更談不上行成Happens Before關(guān)系了伞租。因此1.7版本ConcurrentHashMap的數(shù)據(jù)弱一致性也得以論證

后記
在寫這篇文章的過程中發(fā)現(xiàn)很多之前“掌握”的知識(shí)其實(shí)非常膚淺贞谓,迫使我查閱各種資料和規(guī)范,也得到了很多大牛的幫助葵诈,即便寫完文章也感覺有各種理解的不恰當(dāng)?shù)牡胤铰阆遥饤壐≡辏瑢W(xué)無(wú)止境作喘,永遠(yuǎn)以學(xué)生的心態(tài)腳踏實(shí)地前進(jìn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末理疙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泞坦,更是在濱河造成了極大的恐慌窖贤,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贰锁,死亡現(xiàn)場(chǎng)離奇詭異赃梧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)豌熄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門授嘀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锣险,你說(shuō)我怎么就攤上這事蹄皱±廊颍” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵巷折,是天一觀的道長(zhǎng)压鉴。 經(jīng)常有香客問我,道長(zhǎng)锻拘,這世上最難降的妖魔是什么油吭? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮逊拍,結(jié)果婚禮上上鞠,老公的妹妹穿的比我還像新娘际邻。我一直安慰自己芯丧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布世曾。 她就那樣靜靜地躺著缨恒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轮听。 梳的紋絲不亂的頭發(fā)上骗露,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音血巍,去河邊找鬼萧锉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛述寡,可吹牛的內(nèi)容都是我干的柿隙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鲫凶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼禀崖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起雷激,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤汤功,失蹤者是張志新(化名)和其女友劉穎诈火,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掸屡,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年然评,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仅财。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沾瓦,死狀恐怖满着,靈堂內(nèi)的尸體忽然破棺而出谦炒,到底是詐尸還是另有隱情,我是刑警寧澤风喇,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布宁改,位于F島的核電站,受9級(jí)特大地震影響魂莫,放射性物質(zhì)發(fā)生泄漏还蹲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一耙考、第九天 我趴在偏房一處隱蔽的房頂上張望谜喊。 院中可真熱鬧,春花似錦倦始、人聲如沸斗遏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诵次。三九已至,卻和暖如春枚碗,著一層夾襖步出監(jiān)牢的瞬間逾一,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工肮雨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遵堵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓怨规,卻偏偏與公主長(zhǎng)得像陌宿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子椅亚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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