深入理解ConcurrentHashMap實現(xiàn)原理

HashMap在put的時候掷贾,插入的元素超過了容量(由負(fù)載因子決定)的范圍就會觸發(fā)擴(kuò)容操作睛榄,就是rehash,這個會重新將原數(shù)組的內(nèi)容重新hash到新的擴(kuò)容數(shù)組中想帅,在多線程的環(huán)境下场靴,存在同時其他的元素也在進(jìn)行put操作,如果hash值相同港准,可能出現(xiàn)同時在同一數(shù)組下用鏈表表示旨剥,造成閉環(huán),導(dǎo)致在get時會出現(xiàn)死循環(huán)浅缸,所以HashMap是線程不安全的轨帜。

線程安全的HashTable

HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下疗杉。因為當(dāng)一個線程訪問HashTable的同步方法時阵谚,其他線程訪問HashTable的同步方法時,可能會進(jìn)入阻塞或輪詢狀態(tài)烟具。如線程A使用put進(jìn)行添加元素梢什,線程B不但不能使用put方法添加元素,并且也不能使用get方法來獲取元素朝聋,所以競爭越激烈效率越低嗡午。

ConcurrentHashMap的鎖分段技術(shù)

HashTable容器在競爭激烈的并發(fā)環(huán)境下表現(xiàn)出效率低下的原因,是因為所有訪問HashTable的線程都必須競爭同一把鎖冀痕,那假如容器里有多把鎖荔睹,每一把鎖用于鎖容器其中一部分?jǐn)?shù)據(jù)狸演,那么當(dāng)多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖競爭僻他,從而可以有效的提高并發(fā)訪問效率宵距,這就是ConcurrentHashMap所使用的鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲吨拗,然后給每一段數(shù)據(jù)配一把鎖满哪,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候,其他段的數(shù)據(jù)也能被其他線程訪問劝篷。

在JDK1.7版本中哨鸭,ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)是由一個Segment數(shù)組和多個HashEntry組成,如下圖所示:

ConcurrentHashMap_jdk1.7.jpg

Segment數(shù)組的意義就是將一個大的table分割成多個小的table來進(jìn)行加鎖娇妓,也就是上面的提到的鎖分段技術(shù)像鸡,而每一個Segment元素存儲的是HashEntry數(shù)組+鏈表,這個和HashMap的數(shù)據(jù)存儲結(jié)構(gòu)一樣

ConcurrentHashMap實現(xiàn)存儲和讀取

1哈恰、存儲put()
ConcurrentHashMap的put操作是直接委托給Segment的put方法只估,直接看Segment的put方法:

V put(K key, int hash, V value, boolean onlyIfAbsent) {  
     lock();  
     try {  
         int c = count;  
         if (c++ > threshold) // ensure capacity  
             rehash();  
         HashEntry<K,V>[] tab = table;  
         int index = hash & (tab.length - 1);  
         HashEntry<K,V> first = tab[index];  
         HashEntry<K,V> e = first;  
         while (e != null && (e.hash != hash || !key.equals(e.key)))  
             e = e.next;  
         V oldValue;  
         if (e != null) {  
             oldValue = e.value;  
             if (!onlyIfAbsent)  
                 e.value = value;  
         }  
         else {  
             oldValue = null;  
             ++modCount;  
             tab[index] = new HashEntry<K,V>(key, hash, first, value);  
             count = c; // write-volatile  
         }  
         return oldValue;  
     } finally {  
         unlock();  
     }  
 }

該方法也是在持有段鎖(鎖定整個segment)的情況下執(zhí)行的,這當(dāng)然是為了并發(fā)的安全蕊蝗,修改數(shù)據(jù)是不能并發(fā)進(jìn)行的仅乓,必須得有個判斷是否超限的語句以確保容量不足時能夠rehash。接著是找是否存在同樣一個key的結(jié)點蓬戚,如果存在就直接替換這個結(jié)點的值。否則創(chuàng)建一個新的結(jié)點并添加到hash鏈的頭部宾抓,這時一定要修改modCount和count的值子漩,同樣修改count的值一定要放在最后一步。put方法調(diào)用了rehash方法石洗,原來segment里面才是真正的HashTable幢泼,即每個segment是一個傳統(tǒng)意義上的HashTable

由于put方法里需要對共享變量進(jìn)行寫入操作,所以為了線程安全讲衫,在操作共享變量時必須得加鎖缕棵。Put方法首先定位到Segment,然后在Segment里進(jìn)行插入操作涉兽。插入操作需要經(jīng)歷兩個步驟招驴,第一步判斷是否需要對Segment里的HashEntry數(shù)組進(jìn)行擴(kuò)容,第二步定位添加元素的位置然后放在HashEntry數(shù)組里枷畏。
是否需要擴(kuò)容别厘。在插入元素前會先判斷Segment里的HashEntry數(shù)組是否超過容量(threshold),如果超過閥值拥诡,數(shù)組進(jìn)行擴(kuò)容触趴。值得一提的是氮发,Segment的擴(kuò)容判斷比HashMap更恰當(dāng),因為HashMap是在插入元素后判斷元素是否已經(jīng)到達(dá)容量的冗懦,如果到達(dá)了就進(jìn)行擴(kuò)容爽冕,但是很有可能擴(kuò)容之后沒有新元素插入,這時HashMap就進(jìn)行了一次無效的擴(kuò)容披蕉。
如何擴(kuò)容颈畸。擴(kuò)容的時候首先會創(chuàng)建一個兩倍于原容量的數(shù)組,然后將原數(shù)組里的元素進(jìn)行再hash后插入到新的數(shù)組里嚣艇。為了高效ConcurrentHashMap不會對整個容器進(jìn)行擴(kuò)容承冰,而只對某個segment進(jìn)行擴(kuò)容

2食零、讀取get()
ConcurrentHashMap的get操作也是委托給Segment的get方法困乒,直接看Segment的get方法:

V get(Object key, int hash) {  
     if (count != 0) { // read-volatile 當(dāng)前桶的數(shù)據(jù)個數(shù)是否為0 
         HashEntry<K,V> e = getFirst(hash);  得到頭節(jié)點
         while (e != null) {  
             if (e.hash == hash && key.equals(e.key)) {  
                 V v = e.value;  
                 if (v != null)  
                     return v;  
                 return readValueUnderLock(e); // recheck  
             }  
             e = e.next;  
         }  
     }  
     returnnull;  
 }

get操作的高效之處在于整個get過程不需要加鎖,除非讀到的值是空的才會加鎖重讀贰谣,我們知道HashTable容器的get方法是需要加鎖的娜搂,那么ConcurrentHashMap的get操作是如何做到不加鎖的呢?原因是它的get方法里將要使用的共享變量都定義成volatile吱抚,如用于統(tǒng)計當(dāng)前Segement大小的count字段和用于存儲值的HashEntry的value百宇。定義成volatile的變量,能夠在線程之間保持可見性秘豹,能夠被多線程同時讀携御,并且保證不會讀到過期的值,但是只能被單線程寫(有一種情況可以被多線程寫既绕,就是寫入的值不依賴于原值)啄刹,在get操作里只需要讀不需要寫共享變量count和value,所以可以不用加鎖凄贩。

3誓军、操作size()
ConcurrentHashMap的做法是先嘗試2次通過不鎖住Segment的方式來統(tǒng)計各個Segment大小,如果統(tǒng)計的過程中疲扎,容器的count發(fā)生了變化昵时,則再采用加鎖的方式來統(tǒng)計所有Segment的大小。那么ConcurrentHashMap是如何判斷在統(tǒng)計的時候容器是否發(fā)生了變化呢椒丧?使用modCount變量壹甥,在put , remove和clean方法里操作元素前都會將變量modCount進(jìn)行加1,那么在統(tǒng)計size前后比較modCount是否發(fā)生變化瓜挽,從而得知容器的大小是否發(fā)生變化盹廷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俄占,更是在濱河造成了極大的恐慌管怠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缸榄,死亡現(xiàn)場離奇詭異渤弛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)甚带,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門她肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹰贵,你說我怎么就攤上這事晴氨。” “怎么了碉输?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵籽前,是天一觀的道長。 經(jīng)常有香客問我敷钾,道長枝哄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任阻荒,我火速辦了婚禮挠锥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侨赡。我一直安慰自己蓖租,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布羊壹。 她就那樣靜靜地躺著菜秦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舶掖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天尔店,我揣著相機(jī)與錄音眨攘,去河邊找鬼。 笑死嚣州,一個胖子當(dāng)著我的面吹牛鲫售,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该肴,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼情竹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匀哄?” 一聲冷哼從身側(cè)響起秦效,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤雏蛮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阱州,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挑秉,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年苔货,在試婚紗的時候發(fā)現(xiàn)自己被綠了犀概。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡夜惭,死狀恐怖姻灶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诈茧,我是刑警寧澤产喉,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站若皱,受9級特大地震影響镊叁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜走触,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一晦譬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧互广,春花似錦敛腌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旅敷,卻和暖如春生棍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳谁。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工涂滴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晴音。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓柔纵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锤躁。 傳聞我的和親對象是個殘疾皇子搁料,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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