Java并發(fā)(06)ConcurrentHashMap詳解

ConcurrentHashMap是實(shí)現(xiàn)了線程安全的HashMap芥丧,在JDK1.7和1.8中實(shí)現(xiàn)的原理有所區(qū)別

Jdk1.7 基于分段鎖的實(shí)現(xiàn)原理

整個 ConcurrentHashMap 由一個個 Segment 組成非区,Segment 代表”部分“或”一段“的意思充活,所以很多地方都會將其描述為分段鎖。注意物蝙,行文中,我很多地方用了“槽”來代表一個 segment。

簡單理解就是杭隙,ConcurrentHashMap 是一個 Segment 數(shù)組,Segment 通過繼承 ReentrantLock 來進(jìn)行加鎖因妙,所以每次需要加鎖的操作鎖住的是一個 segment痰憎,這樣只要保證每個 Segment 是線程安全的票髓,也就實(shí)現(xiàn)了全局的線程安全。

concurrencyLevel:并行級別铣耘、并發(fā)數(shù)洽沟、Segment 數(shù),怎么翻譯不重要蜗细,理解它裆操。默認(rèn)是 16,也就是說 ConcurrentHashMap 有 16 個 Segments炉媒,所以理論上踪区,這個時候,最多可以同時支持 16 個線程并發(fā)寫橱野,只要它們的操作分別分布在不同的 Segment 上朽缴。這個值可以在初始化的時候設(shè)置為其他值,但是一旦初始化以后水援,它是不可以擴(kuò)容的密强。每個 Segment 內(nèi)部,其實(shí)每個 Segment 很像之前介紹的 HashMap蜗元,不過它要保證線程安全或渤,所以處理起來要麻煩些

  • 查詢操作:
    • 過程中是沒有加鎖的,那自然我們就需要去考慮并發(fā)問題
  • 更新操作:
    • 添加節(jié)點(diǎn)的操作 put 和刪除節(jié)點(diǎn)的操作 remove 都是要加 segment 上的獨(dú)占鎖的奕扣,所以它們之間自然不會有問題薪鹦,我們需要考慮的問題就是 get 的時候在同一個 segment 中發(fā)生了 put 或 remove 操作。
    • 初始化槽惯豆,這個我們之前就說過了池磁,使用了 CAS 來初始化 Segment 中的數(shù)組。
    • 添加節(jié)點(diǎn)到鏈表的操作是插入到表頭的楷兽,所以地熄,如果這個時候 get 操作在鏈表遍歷的過程已經(jīng)到了中間,是不會影響的芯杀。當(dāng)然端考,另一個并發(fā)問題就是 get 操作在 put 之后,需要保證剛剛插入表頭的節(jié)點(diǎn)被讀取揭厚,這個依賴于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject却特。
    • 擴(kuò)容。擴(kuò)容是新創(chuàng)建了數(shù)組筛圆,然后進(jìn)行遷移數(shù)據(jù)裂明,最后面將 newTable 設(shè)置給屬性 table。所以太援,如果 get 操作此時也在進(jìn)行闽晦,那么也沒關(guān)系轰绵,如果 get 先行,那么就是在舊的 table 上做查詢操作尼荆;而 put 先行,那么 put 操作的可見性保證就是 table 使用了 volatile 關(guān)鍵字唧垦。
  • 刪除操作:
    • get 操作需要遍歷鏈表捅儒,但是 remove 操作會”破壞”鏈表。
      如果 remove 破壞的節(jié)點(diǎn) get 操作已經(jīng)過去了振亮,那么這里不存在任何問題巧还。
      如果 remove 先破壞了一個節(jié)點(diǎn),分兩種情況考慮坊秸。 1麸祷、如果此節(jié)點(diǎn)是頭結(jié)點(diǎn),那么需要將頭結(jié)點(diǎn)的 next 設(shè) 置為數(shù)組該位置的元素褒搔,table 雖然使用了 volatile 修飾阶牍,但是 volatile 并不能提供數(shù)組內(nèi)部操作的可見性保證,所以源碼中使用了 UNSAFE 來操作數(shù)組星瘾,請看方法 setEntryAt走孽。2、如果要刪除的節(jié)點(diǎn)不是頭結(jié)點(diǎn)琳状,它會將要刪除節(jié)點(diǎn)的后繼節(jié)點(diǎn)接到前驅(qū)節(jié)點(diǎn)中磕瓷,這里的并發(fā)保證就是 next 屬性是 volatile 的。

Jdk 1.8基于CAS的實(shí)現(xiàn)原理

Java 7為實(shí)現(xiàn)并行訪問念逞,引入了Segment這一結(jié)構(gòu)困食,實(shí)現(xiàn)了分段鎖,理論上最大并發(fā)度與Segment個數(shù)相等翎承。Java 8為進(jìn)一步提高并發(fā)性硕盹,摒棄了分段鎖的方案,而是直接使用一個大的數(shù)組审洞。同時為了提高哈希碰撞下的尋址性能莱睁,Java 8在鏈表長度超過一定閾值(8)時將鏈表(尋址時間復(fù)雜度為O(N))轉(zhuǎn)換為紅黑樹(尋址時間復(fù)雜度為O(long(N)))。其數(shù)據(jù)結(jié)構(gòu)如下圖所示:

Java 8的ConcurrentHashMap同樣是通過Key的哈希值與數(shù)組長度取模確定該Key在數(shù)組中的索引芒澜。同樣為了避免不太好的Key的hashCode設(shè)計(jì)仰剿,它通過如下方法計(jì)算得到Key的最終哈希值。不同的是痴晦,Java 8的ConcurrentHashMap作者認(rèn)為引入紅黑樹后南吮,即使哈希沖突比較嚴(yán)重,尋址效率也足夠高誊酌,所以作者并未在哈希值的計(jì)算上做過多設(shè)計(jì)部凑,只是將Key的hashCode值與其高16位作異或并保證最高位為0(從而保證最終結(jié)果為正整數(shù))露乏。

更新操作

  • 對于put操作,如果Key對應(yīng)的數(shù)組元素為null涂邀,則通過CAS操作將其設(shè)置為當(dāng)前值瘟仿。如果Key對應(yīng)的數(shù)組元素(也即鏈表表頭或者樹的根元素)不為null,則對該元素使用synchronized關(guān)鍵字申請鎖比勉,然后進(jìn)行操作劳较。如果該put操作使得當(dāng)前鏈表長度超過一定閾值,則將該鏈表轉(zhuǎn)換為樹浩聋,從而提高尋址效率观蜗。
    查詢操作
  • 對于讀操作,由于數(shù)組被volatile關(guān)鍵字修飾衣洁,因此不用擔(dān)心數(shù)組的可見性問題墓捻。同時每個元素是一個Node實(shí)例(Java 7中每個元素是一個HashEntry),它的Key值和hash值都由final修飾坊夫,不可變更砖第,無須關(guān)心它們被修改后的可見性問題。而其Value及對下一個元素的引用由volatile修飾环凿,可見性也有保障厂画。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拷邢,隨后出現(xiàn)的幾起案子袱院,更是在濱河造成了極大的恐慌,老刑警劉巖瞭稼,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忽洛,死亡現(xiàn)場離奇詭異,居然都是意外死亡环肘,警方通過查閱死者的電腦和手機(jī)欲虚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悔雹,“玉大人复哆,你說我怎么就攤上這事‰缌悖” “怎么了梯找?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長益涧。 經(jīng)常有香客問我锈锤,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任久免,我火速辦了婚禮浅辙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阎姥。我一直安慰自己记舆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布呼巴。 她就那樣靜靜地躺著氨淌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伊磺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天删咱,我揣著相機(jī)與錄音屑埋,去河邊找鬼。 笑死痰滋,一個胖子當(dāng)著我的面吹牛摘能,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敲街,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼团搞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了多艇?” 一聲冷哼從身側(cè)響起逻恐,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峻黍,沒想到半個月后复隆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姆涩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年挽拂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骨饿。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡亏栈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宏赘,到底是詐尸還是另有隱情绒北,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布察署,位于F島的核電站镇饮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜储藐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一俱济、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钙勃,春花似錦蛛碌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至克饶,卻和暖如春酝蜒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矾湃。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工亡脑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邀跃。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓霉咨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拍屑。 傳聞我的和親對象是個殘疾皇子途戒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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