HashMap不是線程安全的,HashTable雖然是線程安全,但是該類所有的方法都用synchronized進行線程安全的控制,在高併發的情況下,同一時刻只有一個線程可以獲取對象監視器,其他線程阻塞或者輪詢等待,在線程競爭激烈的情況下,這種方式的效率會非常的低下。
HashTable在擴容的時候,newSize = 2 * oldSize + 1;
ConcurrentHashMap是線程安全的,使用了鎖分段的思想提高了併發度。
ConcurrentHashMap爲什麼高效:
Hashtable低效主要是因爲所有訪問Hashtable的線程都爭奪一把鎖。如果容器有很多把鎖,每一把鎖控制容器中的一部分數據,那麼當多個線程訪問容器裏的不同部分的數據時,線程之前就不會存在鎖的競爭,這樣就可以有效的提高併發的訪問效率。這也正是ConcurrentHashMap使用的分段鎖技術。將ConcurrentHashMap容器的數據分段存儲,每一段數據分配一個Segment(鎖),當線程佔用其中一個Segment時,其他線程可正常訪問其他段數據。
jdk1.7下ConcurrentHashMap的數據結構:
ConcurrentHashMap包含一個Segment數組,每個Segment包含一個HashEntry數組,當修改HashEntry數組採用開鏈法處理衝突,所以它的每個HashEntry元素又是鏈表結構的元素。
查找元素時,先通過key定位到Segment的下標位置,再找到對應的HashEntry的下標位置,然後再比較key的值。
Segment是ConcurrentHashMap的一個內部類,主要組成如下:
HashEnrty源碼:
和HashMap非常類似,唯一的區別是核心的數據如value,以及鏈表都是volatile修飾的,保證了獲取時的可見性。
原理上來說,ConcurrentHashMap採用了分段鎖的技術,不會像HashTable那樣不管put和get都需要同步處理,
理論上ConcurrentHashMap支持Segment數組數量大小的線程併發,當一個線程佔用鎖訪問Segment時不會影
響到其他的Segment。
put()方法:
首先通過key定位到Segment
在具體的Segment再進行put,首先第一步的時候會嘗試獲取鎖,如果獲取失敗肯定就有其他線程存在競爭,則利用
scanAndLockForPut()自旋獲取鎖:
1.嘗試自旋獲取鎖
2.如果重試次數達到了MAX_SCAN_RETRIES則改爲阻塞鎖獲取成功,保證能獲取成功
get()方法:
get的邏輯比較簡單,key通過hash之後定位到具體的Segment,
再通過一次 Hash 定位到具體的元素上。
由於 HashEntry 中的 value 屬性是用 volatile 關鍵詞修飾的,保證了內存可見性,所以每次獲取時都是最新值。
ConcurrentHashMap 的 get 方法是非常高效的,因爲整個過程都不需要加鎖。