進一步瞭解HashMap

jdk1.8的HashMap採用的是數組+鏈表或紅黑樹
這麼做的目的,我們上篇文章已經解釋了一部分:
① 解決鏈表逆序和死循環的問題
② 優化發生hash碰撞情況下,get速度。時間由O(n)提升到O(logn)。

這裏HashMap的鏈表和紅黑樹是隨着鏈表的長度或樹的深度動態轉換的:
當鏈表的長度超過8時,自動轉爲紅黑樹。
當紅黑樹的深度小於6時,自動轉爲鏈表。

爲什麼轉爲紅黑樹是8,而轉爲鏈表爲6?爲什麼兩者不能都設置爲8呢?
首先,轉爲紅黑樹,設置成8,是同過一定的概率總結歸納出來,當鏈表的長度超過8,纔會影響HashMap的get速度,或者如果設置的小轉換提升效率不高;如果設置過大,那麼會直接影響效率。
其次,如果設置成一樣,會在8附近頻繁切換,影響效率。

HashMap線程安全問題解決
大家都知道HashMap不是線程安全。
jdk1.7多線程操作HashMap會造成死循環、數據丟失和數據覆蓋;jdk1.8會造成數據覆蓋。
舉個例子:兩個線程A和B,A和B都想插入數據,且兩個插入的位置爲同一個桶位Index,兩個線程讀取時發現這個位置都是空的,可以直接進行插入操作。當其中一個線程A操作完,將數據插入index這個位置;線程B再去插入時就會將數據覆蓋掉。

平時如何解決線程不安全的位置
使用HashTable和Collections.synchronizedMap以及ConcurrentHashMap來實現線程安全的Map。
1、HashTable直接在方法上加關鍵字synchronized,將整個數組鎖起來,鎖粒度比較大。
2、Collections.synchronizedMap 使用Collections集合工具的內部類,通過傳入 Map封裝出一個synchronizedMap對象,內部定義了一個對象鎖,方法內通過對象鎖實現。
3、ConcurrentHashMap通過分段鎖實現,降低鎖粒度,大大提高併發度。(jdk1.8後放棄了分段鎖)

jdk8裏ConcurrentHashMap爲什麼放棄了分段鎖?
首先,我們先了解下ConcurrentHashMap中的分段鎖實現原理:
ConcurrentHashMap成員變量使用volatile修飾,免除了指令重排序,同時保證內存可見性,另外使用CAS操作和synchronized結合實現賦值操作,多線程操作只會鎖住當前索引的節點。
ConcurrentHashMap的分段一開始就已經確定,後期是不能擴容的。每段稱爲segment,這個segment是可以擴容的,它的內部結構1.7是數組+鏈表的形式,1.8爲數組+鏈表/紅黑樹。
Segment繼承了可重入鎖ReentrantLock,有了鎖的功能,每個鎖控制一段,當segment越來越大時,鎖的粒度也會變得越來越大。

其次,來了解它的優缺點:
分段鎖的優點:

可保證不同段的map同時操作,併發執行,操作同段時競爭和等待。相對於對整個map進行synchronized同步操作是有優勢的。
分段鎖缺點:

①分成很多段比較浪費空間(不連續、碎片化)。

②同時競爭同一段鎖的概率比較小,反而會造成更新等操作長時間等待。

③ 某個段很大時,會造成分段鎖性能下降。

jdk8裏ConcurrentHashMap爲什麼使用了synchronized而不是用ReentrantLock?
①減少內存開銷。可重入鎖ReentrantLock使用,需要繼承AQS來實現同步操作,會增加額外的內存開銷。而jdk1.8中只需要對頭節點進行同步。
②內部優化。可重入鎖ReentrantLock是Api級別的,優化空間小。而synchronized是JVM直接支持的,JVM能在運行時做出相應的優化:鎖粗化,鎖消除,鎖自旋等等。
 

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