ConcurrentHashMap底層原理?

本文爲面試必備系列篇,不深入敘述,具體細節可自行查詢。

可能會問的問題

1、用過ConcurrentHashMap嗎?
2、爲什麼要用ConcurrentHashMap
3、HashMapHashTable的區別,引出ConcurrentHashMap
4、HashMap在多線程環境下存在線程安全問題,那你一般都是怎麼處理這種情況的?
5、能說一下ConcurrentHashMap是怎麼實現的嗎?

爲什麼要用ConcurrentHashMap?

在併發編程中使用HashMap可能會導致程序陷入死循環,而使用線程安全的HashTable效率又非常低,所以採用了ConcurrentHashMap

單看這個回答,就會牽扯到「爲和編髮編程中使用HashMap會導致程序陷入死循環?」和「HashTable爲何效率低下?」這兩個問題,具體可參考上篇 > 面試必備:HashMap底層數據結構?jdk1.8算法優化,hash衝突,擴容等問題

關於ConcurrentHashMap實現原理的兩個參考回答,自己可以重新組織一下:

ConcurrentHashMap採用的是分段式鎖,與之對應的就是HashTableHashTable使用的是Synchronize關鍵字,是對一個大的數組加一把鎖,其實是對對象加鎖,鎖住的是對象整體,性能肯定是比較差的,現在ConcurrentHashMap是將大數組拆分成許多的小數組,每一個小數組擁有一把鎖,允許多個修改操作併發進行。

ConcurrentHashMap採用的是分段式鎖,可以理解爲把一個大的Map拆封成N個小的Segment,在put數據時會根據hash來確定具體存放在哪個Segment中,Segment內部的同步機制是基於Lock操作的,每一個Segment都會分配一把鎖,當線程佔用鎖訪問其中一段數據時,其他段的數據也能被其他線程訪問,也就是實現併發訪問。

繼續拓展,分段式鎖是如何實現的?

ConcurrentHashMapJDK1.7JDK1.8之間是有區別的,當然,這個問題也可以這樣問:

能說一下ConcurrentHashMap在JDK1.7和JDK1.8中的區別嗎?

1、JDK1.7:

HashEntry數組 + Segment數組 + Unsafe 「大量方法運用」

JDK1.7中數據結構是由一個Segment數組和多個HashEntry數組組成的,每一個Segment元素中存儲的是HashEntry數組+鏈表,而且每個Segment均繼承自可重入鎖ReentrantLock,也就帶有了鎖的功能,當線程執行put的時候,只鎖住對應的那個Segment 對象,對其他的 Segmentget put 互不干擾,這樣子就提升了效率,做到了線程安全。

額外補充:我們對 ConcurrentHashMap 最關心的地方莫過於如何解決 HashMapput 時候擴容引起的不安全問題?

JDK1.7ConcurrentHashMapput 方法中進行了兩次 hash 計算去定位數據的存儲位置,儘可能的減小哈希衝突的可能行,然後再根據 hash 值以 Unsafe 調用方式,直接獲取相應的 Segment,最終將數據添加到容器中是由 segment對象的 put 方法來完成。由於 Segment 對象本身就是一把鎖,所以在新增數據的時候,相應的 Segment對象塊是被鎖住的,其他線程並不能操作這個 Segment 對象,這樣就保證了數據的安全性,在擴容時也是這樣的,在 JDK1.7 中的 ConcurrentHashMap擴容只是針對 Segment 對象中的 HashEntry 數組進行擴容,還是因爲 Segment 對象是一把鎖,所以在 rehash 的過程中,其他線程無法對 segmenthash 表做操作,這就解決了 HashMapput 數據引起的閉環問題。

2、JDK1.8:

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呢?

1、隨着JDK版本的迭代,本着對Synchronize不放棄的態度,內置的Synchronize變的越來越“輕”了,某些場合比使用API更加靈活。

2、加鎖力度的不同,在JDK1.7中加鎖的力度是基於Segment的,包含多個HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節點),也就是1.8中加鎖力度更低了,在粗粒度加鎖中 ReentrantLock 可能通過 Condition 來控制各個低粒度的邊界,更加的靈活,而在低粒度中,Condition的優勢就沒有了,所以使用內置的 Synchronize 並不比ReentrantLock效果差。

18年專科畢業後,期間一度迷茫,最近我創建了一個公衆號用來記錄自己的成長。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章