ConcurrentHashMap完全允許多個讀操作並發進行,讀操作並不需要加鎖。(事實上,ConcurrentHashMap支持完全並發的讀以及一定程度並發的寫。)如果使用傳統的技術,如HashMap中的實現,如果允許可以在hash鏈的中間添加或刪除元素,讀操作不加鎖將得到不一致的數據。但是ConcurrentHashMap實現技術是保證HashEntry幾乎是不可變的。HashEntry代表每個hash鏈中的一個節點,其結構如下所示:
- static final class HashEntry<K,V> {
- final K key;
- final int hash;
- volatile V value;
- final HashEntry<K,V> next;
- HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
- this.key = key;
- this.hash = hash;
- this.next = next;
- this.value = value;
- }
- @SuppressWarnings("unchecked")
- static final <K,V> HashEntry<K,V>[] newArray(int i) {
- return new HashEntry[i];
- }
- }
可以看到除了value不是final的,其它值都是final的,這意味着不能從hash鏈的中間或尾部添加或刪除節點,因為這需要修改next引用值,所有的節點的修改只能從頭部開始。對於put操作,可以一律添加到Hash鏈的頭部。但是對於remove操作,可能需要從中間刪除一個節點,這就需要將要刪除節點的前面所有節點整個復制一遍,最后一個節點指向要刪除結點的下一個結點。為了確保讀操作能夠看到最新的值,將value設置成volatile,這避免了加鎖。 remove操作要注意一個問題:如果某個讀操作在刪除時已經定位到了舊的鏈表上,那么此操作仍然將能讀到數據,只不過讀取到的是舊數據而已,這在多線程里面是沒有問題的。
HashEntry 類的 value 域被聲明為 Volatile 型,Java 的內存模型可以保證:某個寫線程對 value 域的寫入馬上可以被后續的某個讀線程“看”到。在 ConcurrentHashMap 中,不允許用 null作為鍵和值,當讀線程讀到某個 HashEntry 的 value 域的值為 null 時,便知道產生了沖突——發生了重排序現象,需要加鎖后重新讀入這個 value 值。這些特性互相配合,使得讀線程即使在不加鎖狀態下,也能正確訪問 ConcurrentHashMap。
在看源碼實現時,對HashEntry 的 value 域的值可能為 null有些疑惑,網上都是說發生了重排序現象,后來仔細想想不完全正確,重排序發生在刪除操作時,這只是其中的一個原因,盡管ConcurrentHashMap不允許將value為null的值加入,但現在仍然能夠讀到一個為空的value就意味着此值對當前線程還不可見,主要因為HashEntry還沒有完全構造完成導致的,所以對添加和刪除(對鏈表的結構性修改都可能會導致value為null)。
轉自:http://blog.csdn.net/enjoyinwind/article/details/41148895