imzoer IT哈哈
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爲何要設置爲不變性,這跟不變性的訪問不須要同步從而節省時間有關。數據結構