ConcurrentHashMap整理

00.png

歡迎訪問我的博客:http://wangnan.tech

參考:

先說說HashMap HashTable

HashMap

  • 線程不安全葱轩,并發(fā)情況下不能使用

HashTable:

  • 線程安全凝果,但是效率低下
  • HashTable容器使用synchronized來保證線程安全释牺,但在線程競爭激烈的情況下HashTable的效率非常低下髓削。因為當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時葡兑,可能會進入阻塞或輪詢狀態(tài)。
  • 線程1使用put方法時赞草,線程2不但不能使用put方法讹堤,也不能使用get方法

ConcurrentHashMap(jdk1.8之前)

簡介

  • 假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數(shù)據(jù)厨疙,那么當多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時洲守,線程間就不會存在鎖競爭,從而可以有效的提高并發(fā)訪問效率沾凄,這就是ConcurrentHashMap所使用的鎖分段技術梗醇,首先將數(shù)據(jù)分成一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖撒蟀,當一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候叙谨,其他段的數(shù)據(jù)也能被其他線程訪問。
  • ConcurrentHashMap是由Segment數(shù)組結構和HashEntry數(shù)組結構組成保屯。Segment是一種可重入鎖ReentrantLock手负,HashEntry則用于存儲鍵值對數(shù)據(jù)。一個ConcurrentHashMap里包含多個Segment數(shù)組姑尺,Segment的結構和HashMap類似竟终,是一種數(shù)組和鏈表結構,一個Segment里包含多個HashEntry數(shù)組切蟋,每個HashEntry是一個鏈表結構的元素统捶,,當對HashEntry數(shù)組的數(shù)據(jù)進行修改時,必須首先獲得它對應的Segment鎖柄粹。
01.png
02.png

ConcurrentHashMap(jdk1.8)

簡介

  • 與JDK6的版本有很大的差異喘鸟。實現(xiàn)線程安全的思想也已經(jīng)完全變了,它摒棄了Segment(鎖段)的概念镰惦,而是啟用了一種全新的方式實現(xiàn),利用CAS算法迷守。它沿用了與它同時期的HashMap版本的思想,底層依然由“數(shù)組”+鏈表+紅黑樹的方式思想旺入,但是為了做到并發(fā)兑凿,又增加了很多輔助的類凯力,例如TreeBin,Traverser等對象內(nèi)部類礼华。

重要屬性

sizeCtl

可以說它是ConcurrentHashMap中出鏡率很高的一個屬性咐鹤,因為它是一個控制標識符,在不同的地方有不同用途圣絮,而且它的取值不同祈惶,也代表不同的含義。

  • 負數(shù)代表正在進行初始化或擴容操作
  • -1代表正在初始化
  • -N 表示有N-1個線程正在進行擴容操作
  • 正數(shù)或0代表hash表還沒有被初始化扮匠,這個數(shù)值表示初始化或下一次進行擴容的大小捧请,這一點類似于擴容閾值的概念。還后面可以看到棒搜,它的值始終是當前ConcurrentHashMap容量的0.75倍疹蛉,這與loadfactor是對應的。實際容量>=sizeCtl力麸,則擴容

concurrencyLevel

concurrencyLevel可款,能夠同時更新ConccurentHashMap且不產(chǎn)生鎖競爭的最大線程數(shù),在Java8之前實際上就是ConcurrentHashMap中的分段鎖個數(shù)克蚂,即Segment[]的數(shù)組長度闺鲸。正確地估計很重要,當?shù)凸腊0龋瑪?shù)據(jù)結構將根據(jù)額外的競爭摸恍,從而導致線程試圖寫入當前鎖定的段時阻塞;相反游盲,如果高估了并發(fā)級別误墓,你遇到過大的膨脹,由于段的不必要的數(shù)量; 這種膨脹可能會導致性能下降益缎,由于高數(shù)緩存未命中谜慌。在Java8里,僅僅是為了兼容舊版本而保留莺奔。唯一的作用就是保證構造map時初始容量不小于concurrencyLevel欣范。

重要內(nèi)部類

Node

Node是最核心的內(nèi)部類,它包裝了key-value鍵值對令哟,所有插入ConcurrentHashMap的數(shù)據(jù)都包裝在這里面恼琼。它與HashMap中的定義很相似,但是有一些差別它對value和next屬性設置了volatile同步鎖屏富,它不允許調(diào)用setValue方法直接改變Node的value域晴竞,它增加了find方法輔助map.get()方法。

TreeNode

  • 鏈表>8狠半,才可能轉(zhuǎn)為TreeNode.
  • HashMap的TreeNode繼承至LinkedHashMap.Entry噩死;而這里繼承至自己實現(xiàn)的Node颤难,將帶有next指針,便于treebin訪問已维。
  • 樹節(jié)點類行嗤,另外一個核心的數(shù)據(jù)結構。當鏈表長度過長的時候垛耳,會轉(zhuǎn)換為TreeNode栅屏。但是與HashMap不相同的是,它并不是直接轉(zhuǎn)換為紅黑樹堂鲜,而是把這些結點包裝成TreeNode放在TreeBin對象中栈雳,由TreeBin完成對紅黑樹的包裝。而且TreeNode在ConcurrentHashMap集成自Node類缔莲,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>類甫恩,也就是說TreeNode帶有next指針,這樣做的目的是方便基于TreeBin的訪問酌予。

TreeBin

  • TreeBin用于封裝維護TreeNode,包含putTreeVal奖慌、lookRoot抛虫、UNlookRoot、remove简僧、balanceInsetion建椰、balanceDeletion等方法
  • 這個類并不負責包裝用戶的key、value信息岛马,而是包裝的很多TreeNode節(jié)點棉姐。它代替了TreeNode的根節(jié)點,也就是說在實際的ConcurrentHashMap“數(shù)組”中啦逆,存放的是TreeBin對象伞矩,而不是TreeNode對象,這是與HashMap的區(qū)別夏志。另外這個類還帶有了讀寫鎖乃坤。

Unsafe與CAS

在ConcurrentHashMap中,隨處可以看到U, 大量使用了U.compareAndSwapXXX的方法沟蔑,這個方法是利用一個CAS算法實現(xiàn)無鎖化的修改值的操作湿诊,他可以大大降低鎖代理的性能消耗。這個算法的基本思想就是不斷地去比較當前內(nèi)存中的變量值與你指定的一個變量值是否相等瘦材,如果相等厅须,則接受你指定的修改的值,否則拒絕你的操作食棕。因為當前線程中的值已經(jīng)不是最新的值朗和,你的修改很可能會覆蓋掉其他線程修改的結果错沽。這一點與樂觀鎖,SVN的思想是比較類似的例隆。

3個原子操作

  1. tabAt // 獲取索引i處Node
  2. casTabAt // 利用CAS算法設置i位置上的Node節(jié)點(將c和table[i]比較甥捺,相同則插入v)。
  3. setTabAt // 設置節(jié)點位置的值镀层,僅在上鎖區(qū)被調(diào)用

put相關

這個put方法依然沿用HashMap的put方法的思想镰禾,根據(jù)hash值計算這個新插入的點在table中的位置i,如果i位置是空的唱逢,直接放進去吴侦,否則進行判斷,如果i位置是樹節(jié)點坞古,按照樹的方式插入新的節(jié)點备韧,否則把i插入到鏈表的末尾。ConcurrentHashMap中依然沿用這個思想痪枫,有一個最重要的不同點就是ConcurrentHashMap不允許key或value為null值织堂。另外由于涉及到多線程,put方法就要復雜一點奶陈。在多線程中可能有以下兩個情況

  • 如果一個或多個線程正在對ConcurrentHashMap進行擴容操作易阳,當前線程也要進入擴容的操作中。這個擴容的操作之所以能被檢測到吃粒,是因為transfer方法中在空結點上插入forward節(jié)點潦俺,如果檢測到需要插入的位置被forward節(jié)點占有,就幫助進行擴容
  • 如果檢測到要插入的節(jié)點是非空且不是forward節(jié)點徐勃,就對這個節(jié)點加鎖事示,這樣就保證了線程安全。盡管這個有一些影響效率僻肖,但是還是會比hashTable的synchronized要好得多

流程:

  1. 判空:null直接拋空指針異常
  2. hash:計算哈希值
  3. 遍歷table
  • 若table為空肖爵,則初始化,僅設置相關參數(shù)臀脏;
  • 計算當前key存放位置遏匆,即table的下標i=(n - 1) & hash;
  • 若待存放位置為null谁榜,casTabAt無鎖插入幅聘;
  • 若是forwarding nodes(檢測到正在擴容),則helpTransfer(幫助其擴容)窃植;
  • else(待插入位置非空且不是forward節(jié)點帝蒿,即碰撞了),將頭節(jié)點上鎖(保證了線程安全):區(qū)分鏈表節(jié)點和樹節(jié)點巷怜,分別插入(遇到hash值與key值都與新節(jié)點一致的情況葛超,只需要更新value值即可暴氏。否則依次向后遍歷,直到鏈表尾插入這個結點)绣张;
  • 若鏈表長度>8答渔,則treeifyBin轉(zhuǎn)樹(Note:若length<64,直接tryPresize,兩倍table.length;不轉(zhuǎn)樹)。
  1. addCount

resize相關

當ConcurrentHashMap容量不足的時候侥涵,需要對table進行擴容沼撕。這個方法的基本思想跟HashMap是很像的,但是由于它是支持并發(fā)擴容的芜飘,所以要復雜的多务豺。原因是它支持多線程進行擴容操作,而并沒有加鎖嗦明。我想這樣做的目的不僅僅是為了滿足concurrent的要求笼沥,而是希望利用并發(fā)處理去減少擴容帶來的時間影響。因為在擴容的時候娶牌,總是會涉及到從一個“數(shù)組”到另一個“數(shù)組”拷貝的操作奔浅,如果這個操作能夠并發(fā)進行,那真真是極好的了诗良。
整個擴容操作分為兩個部分:

  • 第一部分是構建一個nextTable,它的容量是原來的兩倍乘凸,這個操作是單線程完成的。這個單線程的保證是通過RESIZE_STAMP_SHIFT這個常量經(jīng)過一次運算來保證的累榜,這個地方在后面會有提到;
  • 第二個部分就是將原來table中的元素復制到nextTable中灵嫌,這里允許多線程進行操作壹罚。

多線程又是如何實現(xiàn)的呢?
遍歷到ForwardingNode節(jié)點((fh = f.hash) == MOVED)寿羞,說明此節(jié)點被處理過了猖凛,直接跳過。這是控制并發(fā)擴容的核心 绪穆。由于給節(jié)點上了鎖辨泳,只允許當前線程完成此節(jié)點的操作,處理完畢后玖院,將對應值設為ForwardingNode(fwd)菠红,其他線程看到forward,直接向后遍歷难菌。如此便完成了多線程的復制工作试溯,也解決了線程安全問題。

Size相關

由于ConcurrentHashMap在統(tǒng)計size時可能正被多個線程操作郊酒,而我們又不可能讓他停下來讓我們計算遇绞,所以只能計量一個估計值键袱。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市摹闽,隨后出現(xiàn)的幾起案子蹄咖,更是在濱河造成了極大的恐慌,老刑警劉巖付鹿,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜汤,死亡現(xiàn)場離奇詭異,居然都是意外死亡倘屹,警方通過查閱死者的電腦和手機银亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纽匙,“玉大人务蝠,你說我怎么就攤上這事≈虻蓿” “怎么了馏段?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長践瓷。 經(jīng)常有香客問我院喜,道長,這世上最難降的妖魔是什么晕翠? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任喷舀,我火速辦了婚禮,結果婚禮上淋肾,老公的妹妹穿的比我還像新娘硫麻。我一直安慰自己,他們只是感情好樊卓,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布拿愧。 她就那樣靜靜地躺著,像睡著了一般碌尔。 火紅的嫁衣襯著肌膚如雪浇辜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天唾戚,我揣著相機與錄音柳洋,去河邊找鬼。 笑死叹坦,一個胖子當著我的面吹牛膳灶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轧钓,長吁一口氣:“原來是場噩夢啊……” “哼序厉!你這毒婦竟也來了?” 一聲冷哼從身側響起毕箍,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弛房,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后而柑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體文捶,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年媒咳,在試婚紗的時候發(fā)現(xiàn)自己被綠了粹排。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡涩澡,死狀恐怖顽耳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妙同,我是刑警寧澤射富,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站粥帚,受9級特大地震影響胰耗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芒涡,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一柴灯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧费尽,春花似錦赠群、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽店枣。三九已至速警,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸯两,已是汗流浹背闷旧。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钧唐,地道東北人忙灼。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親该园。 傳聞我的和親對象是個殘疾皇子酸舍,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

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