Hashtable與ConcurrentHashMap區別(轉)

ConcurrentHashMap融合了hashtable和hashmap二者的優勢。

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爲什麼要設置爲不變性,這跟不變性的訪問不需要同步從而節省時間有關。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章