本文爲面試必備系列篇,不深刻敘述,具體細節可自行查詢。
一、用過ConcurrentHashMap嗎?
二、爲何要用ConcurrentHashMap?
三、HashMap與HashTable的區別,引出ConcurrentHashMap…
四、HashMap在多線程環境下存在線程安全問題,那你通常都是怎麼處理這種狀況的?
五、能說一下ConcurrentHashMap是怎麼實現的嗎?html
爲何要用ConcurrentHashMap?面試
在併發編程中使用HashMap可能會致使程序陷入死循環,而使用線程安全的HashTable效率又很是低,因此採用了ConcurrentHashMap。算法
單看這個回答,就會牽扯到「爲和編髮編程中使用HashMap會致使程序陷入死循環?」和「HashTable爲什麼效率低下?」這兩個問題,具體可參考上篇 > 面試必備:HashMap底層數據結構?jdk1.8算法優化,hash衝突,擴容等問題編程
關於ConcurrentHashMap實現原理的兩個參考回答,本身能夠從新組織一下:數組
ConcurrentHashMap採用的是分段式鎖,與之對應的就是HashTable,HashTable使用的是Synchronize關鍵字,是對一個大的數組加一把鎖,實際上是對對象加鎖,鎖住的是對象總體,性能確定是比較差的,如今ConcurrentHashMap是將大數組拆分紅許多的小數組,每個小數組擁有一把鎖,容許多個修改操做併發進行。安全
ConcurrentHashMap採用的是分段式鎖,能夠理解爲把一個大的Map拆封成N個小的Segment,在put數據時會根據hash<key>來肯定具體存放在哪一個Segment中,Segment內部的同步機制是基於Lock操做的,每個Segment都會分配一把鎖,當線程佔用鎖訪問其中一段數據時,其餘段的數據也能被其餘線程訪問,也就是實現併發訪問。數據結構
ConcurrentHashMap在JDK1.7和JDK1.8之間是有區別的,固然,這個問題也能夠這樣問:多線程
能說一下ConcurrentHashMap在JDK1.7和JDK1.8中的區別嗎?併發
HashEntry數組 + Segment數組 + Unsafe 「大量方法運用」性能
JDK1.7中數據結構是由一個Segment數組和多個HashEntry數組組成的,每個Segment元素中存儲的是HashEntry數組+鏈表,並且每一個Segment均繼承自可重入鎖ReentrantLock,也就帶有了鎖的功能,當線程執行put的時候,只鎖住對應的那個Segment 對象,對其餘的 Segment 的 get put 互不干擾,這樣子就提高了效率,作到了線程安全。
額外補充:咱們對 ConcurrentHashMap 最關心的地方莫過於如何解決 HashMap 在 put 時候擴容引發的不安全問題?
在 JDK1.7 中 ConcurrentHashMap 在 put 方法中進行了兩次 hash 計算去定位數據的存儲位置,儘量的減少哈希衝突的可能行,而後再根據 hash 值以 Unsafe 調用方式,直接獲取相應的 Segment,最終將數據添加到容器中是由 segment對象的 put 方法來完成。因爲 Segment 對象自己就是一把鎖,因此在新增數據的時候,相應的 Segment對象塊是被鎖住的,其餘線程並不能操做這個 Segment 對象,這樣就保證了數據的安全性,在擴容時也是這樣的,在 JDK1.7 中的 ConcurrentHashMap擴容只是針對 Segment 對象中的 HashEntry 數組進行擴容,仍是由於 Segment 對象是一把鎖,因此在 rehash 的過程當中,其餘線程沒法對 segment 的 hash 表作操做,這就解決了 HashMap 中 put 數據引發的閉環問題。
JDK1.7:ReentrantLock+Segment+HashEntry
JDK1.8:Synchronized+CAS+Node+紅黑樹
JDK1.8屏蔽了JDK1.7中的Segment概念呢,而是直接使用「Node數組+鏈表+紅黑樹」的數據結構來實現,併發控制採用 「Synchronized + CAS機制」來確保安全性,爲了兼容舊版本保留了Segment的定義,雖然沒有任何結構上的做用。
總之JDK1.8中優化了兩個部分:
放棄了 HashEntry 結構而是採用了跟 HashMap 結構很是類似的 Node 數組 + 鏈表(鏈表長度大於8時轉成紅黑樹)的形式
Synchronize替代了ReentrantLock,咱們一直固有的思想可能以爲,Synchronize是重量級鎖,效率比較低,但爲何要替換掉ReentrantLock呢?
一、隨着JDK版本的迭代,本着對Synchronize不放棄的態度,內置的Synchronize變的愈來愈「輕」了,某些場合比使用API更加靈活。
二、加鎖力度的不一樣,在JDK1.7中加鎖的力度是基於Segment的,包含多個HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節點),也就是1.8中加鎖力度更低了,在粗粒度加鎖中 ReentrantLock 可能經過 Condition 來控制各個低粒度的邊界,更加的靈活,而在低粒度中,Condition的優點就沒有了,因此使用內置的 Synchronize 並不比ReentrantLock效果差。
18年專科畢業後,期間一度迷茫,最近我建立了一個公衆號用來記錄本身的成長。