通過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以併發進行。
有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢後,又按順序釋放所有段的鎖。這裏“按順序”是很重要的,否則極有可能出現死鎖,在ConcurrentHashMap內部,段數組是final的,並且其成員變量實際上也是final的,但是,僅僅是將數組聲明爲final的並不保證數組成員也是final的,這需要實現上的保證。這可以確保不會出現死鎖,因爲獲得鎖的順序是固定的。一、結構解析
對比上圖,HashTable實現鎖的方式是鎖整個hash表,而ConcurrentHashMap的實現方式是鎖桶(簡單理解就是將整個hash表想象成一大缸水,現在將這大缸裏的水分到了幾個水桶裏,hashTable每次都鎖定這個大缸,而ConcurrentHashMap則每次只鎖定其中一個 桶)。
ConcurrentHashMap將hash表分爲16個桶(默認值),諸如get,put,remove等常用操作只鎖當前需要用到的桶。試想,原來 只能一個線程進入,現在卻能同時16個寫線程進入,併發性的提升是顯而易見的。
ConcurrentHashMap和Hashtable主要區別就是圍繞着鎖的粒度以及如何鎖,可以簡單理解成把一個大的HashTable分解成多個,形成了鎖分離。
而Hashtable的實現方式是---鎖整個hash表
ConCurrentHashMap remove()方法
當對ConcurrentHashMap進行remove操作時,並不是進行簡單的節點刪除操作,對比上圖,當對ConcurrentHashMap的一個segment也就是一個桶中的節點進行remove後,例如刪除節點C,C節點實際並沒有被銷燬,而是將C節點前面的反轉並拷貝到新的鏈表中,C節點後面的不需要被克隆。這樣來保持併發的讀線程不受併發的寫線程的干擾。例如現在有一個讀線程讀到了A節點,寫線程把C刪掉了,但是看上圖,讀線程仍然可以繼續讀下去;當然,如果在刪除C之前讀線程讀到的是D,那麼更不會有影響。ConcurrentHashMap中刪除一個節點並不會立刻被讀線程感受到的效果,就是傳說中的弱一致性,所以ConcurrentHashMap的迭代器是弱一致性迭代器。下面是刪除的圖:
那爲什麼是倒序的呢?
是因爲hashentry 結構的額不可變性,即第一次設置next屬性之後就不在改變,所以是需要克隆下來的,但是你要刪除C點你肯定是從C忘頭結點走、
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;
}
可以看到除了value不是final的,其它值都是final的,這意味着不能從hash鏈的中間或尾部添加或刪除節點,因爲這需要修改next 引用值,所有的節點的修改只能從頭部開始。對於put操作,可以一律添加到Hash鏈的頭部。但是對於remove操作,可能需要從中間刪除一個節點,這就需要將要刪除節點的前面所有節點整個複製一遍,最後一個節點指向要刪除結點的下一個結點。