hashtable是做了同步的,hashmap未考慮同步。是以hashmap在單線程情況下效率較高。hashtable在的多線程情況下,同步操作能保證程式執行的正确性。
但是hashtable每次同步執行的時候都要鎖住整個結構。看下圖:

圖左側清晰的标注出來,lock每次都要鎖住整個結構。
concurrenthashmap正是為了解決這個問題而誕生的。
concurrenthashmap鎖的方式是稍微細粒度的。 concurrenthashmap将hash表分為16個桶(預設值),諸如get,put,remove等常用操作隻鎖目前需要用到的桶。
試想,原來 隻能一個線程進入,現在卻能同時16個寫線程進入(寫線程才需要鎖定,而讀線程幾乎不受限制,之後會提到),并發性的提升是顯而易見的。
更令人驚訝的是concurrenthashmap的讀取并發,因為在讀取的大多數時候都沒有用到鎖定,是以讀取操作幾乎是完全的并發操作,而寫操作鎖定的粒度又非常細,比起之前又更加快速(這一點在桶更多時表現得更明顯些)。隻有在求size等操作時才需要鎖定整個表。
而在疊代時,concurrenthashmap使用了不同于傳統集合的快速失敗疊代器的另一種疊代方式,我們稱為弱一緻疊代器。在這種疊代方式中,當iterator被建立後集合再發生改變就不再是抛出 concurrentmodificationexception,取而代之的是在改變時new新的資料進而不影響原有的數
據,iterator完成後再将頭指針替換為新的資料,這樣iterator線程可以使用原來老的資料,而寫線程也可以并發的完成改變,更重要的,這保證了多個線程并發執行的連續性和擴充性,是性能提升的關鍵。
下面分析concurrenthashmap的源碼。主要是分析其中的segment。因為操作基本上都是在segment上的。先看segment内部資料的定義。
從上圖可以看出,很重要的一個是table變量。是一個hashentry的數組。segment就是把資料存放在這個數組中的。除了這個量,還有諸如loadfactor、modcount等變量。
看segment的get 函數的實作:
加上hashentry的代碼:
可以看出,hashentry是一個連結清單型的資料結構。
在segment的get函數中,通過getfirst函數得到第一個值,然後就是通過這個值的next,一路找到想要的那個對象。如果不空,則傳回。如果為空,則可能是其他線程正在修改節點。比如上面說的弱一緻疊代器在将指針更改為新值的過程。而之前的 get操作都未進行鎖定,根據bernstein條件,讀後寫或寫後讀都會引起資料的不一緻,是以這裡要對這個e重新上鎖再讀一遍,以保證得到的是正确值。readvalueunderlock中就是用了lock()進行加鎖。
put操作已開始就鎖住了整個segment。這是因為修改操作時不能并發的。
同樣,remove操作也是如此(類似put,一開始就鎖住真個segment)。
但要注意一點差別,中間那個for循環是做什麼用的呢?(截圖未完全,可以自己找找代碼檢視一下)。從代碼來看,就是将定位之後的所有entry克隆并拼回前面去,但有必要嗎?每次删除一個元素就要将那之前的元素克隆一遍?這點其實是由entry的不變性來決定的,仔細觀察entry定義,發現除了value,其他 所有屬性都是用final來修飾的,這意味着在第一次設定了next域之後便不能再改變它,取而代之的是将它之前的節點全都克隆一次。至于entry為什麼要設定為不變性,這跟不變性的通路不需要同步進而節省時間有關。