ConcurrentHashMap底层原理?
本文為面試必備系列篇,不深入敘述,具體細(xì)節(jié)可自行查詢。
可能會問的問題
1、用過ConcurrentHashMap嗎?
2、為什么要用ConcurrentHashMap?
3、HashMap與HashTable的區(qū)別,引出ConcurrentHashMap…
4、HashMap在多線程環(huán)境下存在線程安全問題,那你一般都是怎么處理這種情況的?
5、能說一下ConcurrentHashMap是怎么實(shí)現(xiàn)的嗎?
為什么要用ConcurrentHashMap?
在并發(fā)編程中使用HashMap可能會導(dǎo)致程序陷入死循環(huán),而使用線程安全的HashTable效率又非常低,所以采用了ConcurrentHashMap。
單看這個(gè)回答,就會牽扯到「為和編發(fā)編程中使用HashMap會導(dǎo)致程序陷入死循環(huán)?」和「HashTable為何效率低下?」這兩個(gè)問題,具體可參考上篇 > 面試必備:HashMap底層數(shù)據(jù)結(jié)構(gòu)?jdk1.8算法優(yōu)化,hash沖突,擴(kuò)容等問題
關(guān)于ConcurrentHashMap實(shí)現(xiàn)原理的兩個(gè)參考回答,自己可以重新組織一下:
ConcurrentHashMap采用的是分段式鎖,與之對應(yīng)的就是HashTable,HashTable使用的是Synchronize關(guān)鍵字,是對一個(gè)大的數(shù)組加一把鎖,其實(shí)是對對象加鎖,鎖住的是對象整體,性能肯定是比較差的,現(xiàn)在ConcurrentHashMap是將大數(shù)組拆分成許多的小數(shù)組,每一個(gè)小數(shù)組擁有一把鎖,允許多個(gè)修改操作并發(fā)進(jìn)行。
ConcurrentHashMap采用的是分段式鎖,可以理解為把一個(gè)大的Map拆封成N個(gè)小的Segment,在put數(shù)據(jù)時(shí)會根據(jù)hash來確定具體存放在哪個(gè)Segment中,Segment內(nèi)部的同步機(jī)制是基于Lock操作的,每一個(gè)Segment都會分配一把鎖,當(dāng)線程占用鎖訪問其中一段數(shù)據(jù)時(shí),其他段的數(shù)據(jù)也能被其他線程訪問,也就是實(shí)現(xiàn)并發(fā)訪問。
繼續(xù)拓展,分段式鎖是如何實(shí)現(xiàn)的?
ConcurrentHashMap在JDK1.7和JDK1.8之間是有區(qū)別的,當(dāng)然,這個(gè)問題也可以這樣問:
能說一下ConcurrentHashMap在JDK1.7和JDK1.8中的區(qū)別嗎?
1、JDK1.7:
HashEntry數(shù)組 + Segment數(shù)組 + Unsafe 「大量方法運(yùn)用」
JDK1.7中數(shù)據(jù)結(jié)構(gòu)是由一個(gè)Segment數(shù)組和多個(gè)HashEntry數(shù)組組成的,每一個(gè)Segment元素中存儲的是HashEntry數(shù)組+鏈表,而且每個(gè)Segment均繼承自可重入鎖ReentrantLock,也就帶有了鎖的功能,當(dāng)線程執(zhí)行put的時(shí)候,只鎖住對應(yīng)的那個(gè)Segment 對象,對其他的 Segment 的 get put 互不干擾,這樣子就提升了效率,做到了線程安全。
額外補(bǔ)充:我們對 ConcurrentHashMap 最關(guān)心的地方莫過于如何解決 HashMap 在 put 時(shí)候擴(kuò)容引起的不安全問題?
在 JDK1.7 中 ConcurrentHashMap 在 put 方法中進(jìn)行了兩次 hash 計(jì)算去定位數(shù)據(jù)的存儲位置,盡可能的減小哈希沖突的可能行,然后再根據(jù) hash 值以 Unsafe 調(diào)用方式,直接獲取相應(yīng)的 Segment,最終將數(shù)據(jù)添加到容器中是由 segment對象的 put 方法來完成。由于 Segment 對象本身就是一把鎖,所以在新增數(shù)據(jù)的時(shí)候,相應(yīng)的 Segment對象塊是被鎖住的,其他線程并不能操作這個(gè) Segment 對象,這樣就保證了數(shù)據(jù)的安全性,在擴(kuò)容時(shí)也是這樣的,在 JDK1.7 中的 ConcurrentHashMap擴(kuò)容只是針對 Segment 對象中的 HashEntry 數(shù)組進(jìn)行擴(kuò)容,還是因?yàn)?Segment 對象是一把鎖,所以在 rehash 的過程中,其他線程無法對 segment 的 hash 表做操作,這就解決了 HashMap 中 put 數(shù)據(jù)引起的閉環(huán)問題。
2、JDK1.8:
JDK1.7:ReentrantLock+Segment+HashEntry
JDK1.8:Synchronized+CAS+Node+紅黑樹
JDK1.8屏蔽了JDK1.7中的Segment概念呢,而是直接使用「Node數(shù)組+鏈表+紅黑樹」的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn),并發(fā)控制采用 「Synchronized + CAS機(jī)制」來確保安全性,為了兼容舊版本保留了Segment的定義,雖然沒有任何結(jié)構(gòu)上的作用。
總之JDK1.8中優(yōu)化了兩個(gè)部分:
放棄了 HashEntry 結(jié)構(gòu)而是采用了跟 HashMap 結(jié)構(gòu)非常相似的 Node數(shù)組 + 鏈表(鏈表長度大于8時(shí)轉(zhuǎn)成紅黑樹)的形式
Synchronize替代了ReentrantLock,我們一直固有的思想可能覺得,Synchronize是重量級鎖,效率比較低,但為什么要替換掉ReentrantLock呢?
1、隨著JDK版本的迭代,本著對Synchronize不放棄的態(tài)度,內(nèi)置的Synchronize變的越來越“輕”了,某些場合比使用API更加靈活。
2、加鎖力度的不同,在JDK1.7中加鎖的力度是基于Segment的,包含多個(gè)HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節(jié)點(diǎn)),也就是1.8中加鎖力度更低了,在粗粒度加鎖中 ReentrantLock 可能通過 Condition 來控制各個(gè)低粒度的邊界,更加的靈活,而在低粒度中,Condition的優(yōu)勢就沒有了,所以使用內(nèi)置的 Synchronize 并不比ReentrantLock效果差。
18年專科畢業(yè)后,期間一度迷茫,最近我創(chuàng)建了一個(gè)公眾號用來記錄自己的成長。
總結(jié)
以上是生活随笔為你收集整理的ConcurrentHashMap底层原理?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JSON.stringify方法详解
- 下一篇: 《redis 设计与实现》读书笔记