ConcurrentHashMap的目標是實現支持高併發、高吞吐量的線程安全的HashMap。當然不能直接對整個hashtable加鎖,所以在ConcurrentHashMap中,數據的組織結構和HashMap有所區別
大家都知道,HashMap中未進行同步考慮,而Hashtable則使用了synchronized,帶來的直接影響就是可選擇,我們可以在單線程時使用HashMap提高效率,而多線程時用Hashtable來保證安全。
當我們享受着jdk帶來的便利時同樣承受它帶來的不幸惡果。通過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,安全的背後是巨大的浪費,慧眼獨具的Doug Lee立馬拿出瞭解決方案----ConcurrentHashMap。
ConcurrentHashMap和Hashtable主要區別就是圍繞着鎖的粒度以及如何鎖。如圖
左邊便是Hashtable的實現方式---鎖整個hash表;而右邊則是ConcurrentHashMap的實現方式---鎖桶(或段)。ConcurrentHashMap將hash表分爲16個桶(默認值),諸如get,put,remove等常用操作只鎖當前需要用到的桶。試想,原來只能一個線程進入,現在卻能同時16個寫線程進入(寫線程才需要鎖定,而讀線程幾乎不受限制,之後會提到),併發性的提升是顯而易見的。
更令人驚訝的是ConcurrentHashMap的讀取併發,因爲在讀取的大多數時候都沒有用到鎖定,所以讀取操作幾乎是完全的併發操作,而寫操作鎖定的粒度又非常細,比起之前又更加快速(這一點在桶更多時表現得更明顯些)。只有在求size等操作時才需要鎖定整個表。而在迭代時,ConcurrentHashMap使用了不同於傳統集合的快速失敗迭代器的另一種迭代方式,我們稱爲弱一致迭代器。在這種迭代方式中,當iterator被創建後集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據,iterator完成後再將頭指針替換爲新的數據,這樣iterator線程可以使用原來老的數據,而寫線程也可以併發的完成改變,更重要的,這保證了多個線程併發執行的連續性和擴展性,是性能提升的關鍵。
接下來,讓我們看看ConcurrentHashMap中的幾個重要方法,心裏知道了實現機制後,使用起來就更加有底氣。
ConcurrentHashMap中主要實體類就是三個:ConcurrentHashMap(整個Hash表),Segment(桶),HashEntry(節點),對應上面的圖可以看出之間的關係。
get方法(請注意,這裏分析的方法都是針對桶的,因爲ConcurrentHashMap的最大改進就是將粒度細化到了桶上),首先判斷了當前桶的數據個數是否爲0,爲0自然不可能get到什麼,只有返回null,這樣做避免了不必要的搜索,也用最小的代價避免出錯。然後得到頭節點(方法將在下面涉及)之後就是根據hash和key逐個判斷是否是指定的值,如果是並且值非空就說明找到了,直接返回;程序非常簡單,但有一個令人困惑的地方,這句return readValueUnderLock(e)到底是用來幹什麼的呢?研究它的代碼,在鎖定之後返回一個值。但這裏已經有一句V v = e.value得到了節點的值,這句return readValueUnderLock(e)是否多此一舉?事實上,這裏完全是爲了併發考慮的,這裏當v爲空時,可能是一個線程正在改變節點,而之前的get操作都未進行鎖定,根據bernstein條件,讀後寫或寫後讀都會引起數據的不一致,所以這裏要對這個e重新上鎖再讀一遍,以保證得到的是正確值,這裏不得不佩服Doug Lee思維的嚴密性。整個get操作只有很少的情況會鎖定,相對於之前的Hashtable,併發是不可避免的啊!
V get(Object key, int hash)
{
if
(count != 0) { //
read-volatile
HashEntry
e =
getFirst(hash);
while
(e != null)
{
if
(e.hash == hash && key.equals(e.key))
{
V
v =
e.value;
if
(v !=
null)
return
v;
return
readValueUnderLock(e); //
recheck
}
e
=
e.next;
}
}
return
null;
}
V readValueUnderLock(HashEntry e)
{
lock();
try
{
return
e.value;
}
finally
{
unlock();
}
}
put操作一上來就鎖定了整個segment,這當然是爲了併發的安全,修改數據是不能併發進行的,必須得有個判斷是否超限的語句以確保容量不足時能夠rehash,而比較難懂的是這句int index = hash & (tab.length - 1),原來segment裏面纔是真正的hashtable,即每個segment是一個傳統意義上的hashtable,如上圖,從兩者的結構就可以看出區別,這裏就是找出需要的entry在table的哪一個位置,之後得到的entry就是這個鏈的第一個節點,如果e!=null,說明找到了,這是就要替換節點的值(onlyIfAbsent == false),否則,我們需要new一個entry,它的後繼是first,而讓tab[index]指向它,什麼意思呢?實際上就是將這個新entry插入到鏈頭,剩下的就非常容易理解了。
V put(K key, int hash, V value, boolean onlyIfAbsent)
{
lock();
try
{
int
c =
count;
if
(c++ > threshold) // ensure
capacity
rehash();
HashEntry[]
tab =
table;
int
index = hash & (tab.length -
1);
HashEntry
first = (HashEntry)
tab[index];
HashEntry
e =
first;
while
(e != null && (e.hash != hash ||
!key.equals(e.key)))
e
= e.next;
V
oldValue;
if
(e != null)
{
oldValue
=
e.value;
if
(!onlyIfAbsent)
e.value
=
value;
}
else
{
oldValue
=
null;
++modCount;
tab[index]
= new HashEntry(key, hash, first,
value);
count
= c; //
write-volatile
}
return
oldValue;
}
finally
{
unlock();
}
}
remove操作非常類似put,但要注意一點區別,中間那個for循環是做什麼用的呢?(*號標記)從代碼來看,就是將定位之後的所有entry克隆並拼回前面去,但有必要嗎?每次刪除一個元素就要將那之前的元素克隆一遍?這點其實是由entry的不變性來決定的,仔細觀察entry定義,發現除了value,其他所有屬性都是用final來修飾的,這意味着在第一次設置了next域之後便不能再改變它,取而代之的是將它之前的節點全都克隆一次。
V remove(Object key, int hash, Object value)
{
lock();
try
{
int
c = count -
1;
HashEntry[]
tab =
table;
int
index = hash & (tab.length -
1);
HashEntry
first =
(HashEntry)tab[index];
HashEntry
e =
first;
while
(e != null && (e.hash != hash ||
!key.equals(e.key)))
e
= e.next;
V
oldValue =
null;
if
(e != null)
{
V
v =
e.value;
if
(value == null || value.equals(v))
{
oldValue
=
v;
//
All entries following removed node can
stay
//
in list, but all preceding ones need to
be
//
cloned.
++modCount;
HashEntry
newFirst =
e.next;
* for
(HashEntry p = first; p != e; p =
p.next)
* newFirst
= new HashEntry(p.key,
p.hash,
newFirst,
p.value);
tab[index]
=
newFirst;
count
= c; //
write-volatile
}
}
return
oldValue;
}
finally
{
unlock();
}
}
static final class HashEntry { final K key; final int hash; volatile V value; final HashEntry next; HashEntry(K key, int hash, HashEntry next, V value) { this.key = key; this.hash = hash; this.next = next; this.value = value; } }
轉載連接:http://blog.csdn.net/liuzhengkang/article/details/2916620