在并發(fā)使用到HashMap的時(shí)候,往往不建議直接用HashMap沸枯,因?yàn)镠ashMap在并發(fā)寫數(shù)據(jù)的時(shí)候容易因?yàn)閞ehash的過程產(chǎn)生環(huán)形鏈表的情況日矫。所以在并發(fā)使用Map結(jié)構(gòu)時(shí),一般建議使用ConcurrentHashMap绑榴。
1 JDK1.7 的ConcurrentHashMap
在JDK1.7中ConcurrentHashMap采用了數(shù)組+Segment+分段鎖的方式實(shí)現(xiàn)哪轿。
- Segment(分段鎖):ConcurrentHashMap中的分段鎖稱為Segment,它即類似于HashMap的結(jié)構(gòu)翔怎,即內(nèi)部擁有一個(gè)Entry數(shù)組窃诉,數(shù)組中的每個(gè)元素又是一個(gè)鏈表,同時(shí)又是一個(gè)ReentrantLock(Segment繼承了ReentrantLock)。
-
內(nèi)部結(jié)構(gòu):ConcurrentHashMap使用分段鎖技術(shù)赤套,將數(shù)據(jù)分成一段一段的存儲(chǔ)飘痛,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問其中一個(gè)段數(shù)據(jù)的時(shí)候于毙,其他段的數(shù)據(jù)也能被其他線程訪問敦冬,能夠?qū)崿F(xiàn)真正的并發(fā)訪問。如下圖是ConcurrentHashMap的內(nèi)部結(jié)構(gòu)圖:
從上面的結(jié)構(gòu)我們可以了解到唯沮,ConcurrentHashMap定位一個(gè)元素的過程需要進(jìn)行兩次Hash操作脖旱。第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部介蛉。
2 JDK1.8之后的ConcurrentHashMap
JDK8中ConcurrentHashMap參考了JDK8 HashMap的實(shí)現(xiàn)萌庆,采用了數(shù)組+鏈表+紅黑樹的實(shí)現(xiàn)方式來設(shè)計(jì),內(nèi)部大量采用CAS操作币旧。并發(fā)控制使?synchronized 和 CAS 來操作践险。(JDK1.6 以后 對(duì) synchronized 鎖做了很多優(yōu)化) 整個(gè)看起來就像是優(yōu)化過且線程安全的 HashMap,雖然在 JDK1.8 中還能看到 Segment 的數(shù)據(jù)結(jié)構(gòu),但
是已經(jīng)簡(jiǎn)化了屬性巍虫,只是為了兼容舊版本彭则;
JDK1.8的Nod節(jié)點(diǎn)中value和next都用volatile修飾,保證并發(fā)的可見性占遥。
可以理解為俯抖,synchronized 只鎖定當(dāng)前鏈表或紅??叉樹的?節(jié)點(diǎn),這樣只要 hash 不沖突瓦胎,就不會(huì)產(chǎn)?并發(fā)芬萍,效率?提升 N 倍。
3 ConcurrentHashMap和HashTable的區(qū)別
Hashtable 和 JDK1.8 之前的 HashMap 的底層數(shù)據(jù)結(jié)構(gòu)類似都是采? 數(shù)組+鏈表 的形式搔啊,數(shù)組是 HashMap 的主體柬祠,鏈表則是主要為了解決哈希沖突?存在的;
Hashtable(同?把鎖) :使? synchronized 來保證線程安全负芋,效率?常低下漫蛔。當(dāng)?個(gè)線程訪問同步?法時(shí),其他線程也訪問同步?法示罗,可能會(huì)進(jìn)?阻塞或輪詢狀態(tài)惩猫,如使? put 添加元素,另?個(gè)線程不能使? put 添加元素蚜点,也不能使?get轧房,競(jìng)爭(zhēng)會(huì)越來越激烈效率越低;
總結(jié)一下:
-
ConcurrentHashMap不論1.7還是1.8绍绘,他的執(zhí)行效率都比HashTable要高的多奶镶,主要原因還是因?yàn)镠ash Table使用了一種全表加鎖的方式。