---->HashMap
在java1.7中,hashmap的數據結構是基於數組+鏈表的結構,即我們比較熟悉的Entry數組,其包含的(key-value)鍵值對的形式。在多線程環境下,HashMap進行put操作會引起死循環,是因為多線程會導致HashMap的Entry鏈表形成環形數據結構,一旦形成環形數據結構,Entry的next節點永遠不為空,就會產生死循環獲取Entry。
Entry是HashMap中的一個靜態內部類。代碼如下
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next;//存儲指向下一個Entry的引用,單鏈表結構 int hash;//對key的hashcode值進行hash運算后得到的值,存儲在Entry,避免重復計算 /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
在java1.8中,hashmap是以 數組+鏈表+紅黑樹,由於有紅黑樹的加入,hashmap性能有了很大程度的優化,但是還是沒辦法解決在並發環境下的線程安全。
---->HashTable
hashtabled 和 hashmap 的實現原理幾乎一樣,差別在於
- HashMap的鍵和值都允許有null值存在,而HashTable則不行
- HashMap是非線程安全的,HashTable是線程安全的
- 在單線程環境下,HashMap的運行效率是要比HashTable要快得多的(因為HashTable是線程安全,但是其實現的安全的策略犧牲代價太大,get/put所有相關操作都是synchronized的,相當於給整個哈希表加了一個大鎖,多線程訪問時候,只要有一個線程訪問或操作該對象時,則其他線程就只能阻塞,相當於將所有的操作串行化)
- Hashtable默認的初始大小為11,之后每次擴充,容量變為原來的2n+1。HashMap默認的初始化大小為16。之后每次擴充,容量變為原來的2倍
-
HashMap的Iterator是fail-fast迭代器。當有其它線程改變了HashMap的結構(增加,刪除,修改元素),將會拋出ConcurrentModificationException。不過,通過Iterator的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並不是一個一定發生的行為,要看JVM。JDK8之前的版本中,Hashtable是沒有fast-fail機制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的。
---->ConcurrentHashMap
在java1.7中,concurrenthashmap的數據結構為 Segment + HashEntry,ConcurrentHashMap的鎖分段技術:假如容器里有多把鎖,每一把鎖用於鎖容器其中一部分數據,那么當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高並發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術。首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。用一個Segment數組維護所有的鍵值對,一個Segment對象的數據結構相當於一個HashMap,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。ConcurrentHashMap中的HashEntry相對於HashMap中的Entry有一定的差異性:HashEntry中的value以及next都被volatile修飾,這樣在多線程讀寫過程中能夠保持它們的可見性,代碼如下:
static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next;
ConcurrentHashMap不允許Key或者Value的值為NULL
在java1.8中,它摒棄了Segment(鎖段)的概念,而是啟用了一種全新的方式實現,利用CAS算法。它沿用了與它同時期的HashMap版本的思想,底層依然由“數組”+鏈表+紅黑樹的方式思想,接采用transient volatile Node<K,V>[] table保存數據,采用table數組元素作為鎖,從而實現了對每一行數據進行加鎖,進一步減少並發沖突的概率。
並且,ConcurrentHashMap相對於HashTable來說,ConcurrentHashMap的很多操作比如get,clear,iterator 都是弱一致性的,而HashTable是強一致性的。
何為弱一致性?
get方法是弱一致的,是什么含義?可能你期望往ConcurrentHashMap底層數據結構中加入一個元素后,立馬能對get可見,但ConcurrentHashMap並不能如你所願。換句話說,put操作將一個元素加入到底層數據結構后,get可能在某段時間內還看不到這個元素,若不考慮內存模型,單從代碼邏輯上來看,卻是應該可以看得到的。
因為沒有全局的鎖,在清除完一個segments之后,正在清理下一個segments的時候,已經清理segments可能又被加入了數據,因此clear返回的時候,ConcurrentHashMap中是可能存在數據的。因此,clear方法是弱一致的。如下:
public void clear() { for (int i = 0; i < segments.length; ++i) segments[i].clear(); }
ConcurrentHashMap的迭代器底層原理中,在遍歷過程中,如果已經遍歷的數組內容發生了變化,迭代器不會拋出ConcurrentModificationException異常。如果未遍歷的數組上的內容發生了變化,則有可能反映到迭代過程中。這就是ConcurrentHashMap迭代器弱一致的表現。
參考:ConcurrentHashMap能完全替代HashTable嗎?