ConcurrentHashMap是面試的重點,尤其是jdk7與8的對比,這里進行總結對比學習。 總結對比圖直接上jdk7與jdk8對ConcurrentHashMap底層實現(xiàn)的對比圖,對比如下圖: jdk7的ConcurrentHashMap底層結構是Segment數(shù)組,可以在初始化的時候指定Segment數(shù)組的長度,并且不可變。而Segment繼承了ReentrantLock,它存儲數(shù)據(jù)的實現(xiàn)與HashMap很像。也就是說jdk7的ConcurrentHashMap可以看成是由線程安全的HashMap組成的一個map數(shù)組,數(shù)組的長度決定了支持的最大的并發(fā)量。 jdk8的ConcurrentHashMap的底層結構與HashMap又一樣了,底層是一個Node的數(shù)組,而通過對Node數(shù)組以CAS方式實現(xiàn)擴容和對Node數(shù)組的每個元素的synchronized保證ConcurrentHashMap整體的線程安全。 jdk8在鏈表數(shù)組結構上進行了優(yōu)化,與HashMap在jdk8的優(yōu)化一樣,當鏈表長度達到8的時候會把鏈表轉成紅黑樹,能夠提高查找性能。至于紅黑樹的特點與優(yōu)點這里就不擴展了,有興趣的同學可以網(wǎng)上去了解。 jdk8put方法實現(xiàn)因為jdk7的ConcurrentHashMap的實現(xiàn)已經(jīng)過時了,所以這里就不詳解了,直接看jdk8的實現(xiàn),put方法依賴putVal方法,所以直接看putVal方法源碼與詳解如下圖: 可以看到putVal有一點點長,但是也并不復雜,主要分計算hash和一個死循環(huán),所以主要在死循環(huán)里面的內(nèi)容,死循環(huán)主要有四種情況: 1、table還沒有初始化,所以初始化table,然后進入下次循環(huán); 2、table初始化了,但是hash對應在table中的位置并沒有初始化,采用CAS方式把當前要put的值設置進這處,設置失敗則進入下次循環(huán),成功則保存成功,退出循環(huán); 3、如果判斷有其他線程正在對ConcurrentHashMap擴容,獲取要去獲取新的tab,進入下次循環(huán); 4、找到了對應節(jié)點f,直接對f加synchronized同步,然后判斷f節(jié)點是鏈表結構還是紅黑樹結構,鏈表結構則遍歷鏈表進行設置,紅黑樹則采用紅黑樹設置進去。設置成功后判斷是否需要把鏈表結構轉紅黑樹; 整體來看put方法還是比較簡單的死循環(huán)加CAS是并發(fā)里面的典型應用可以保證table的線程安全,然后找到對應節(jié)點f進行同步再次保證每個節(jié)點線程安全,所以ConcurrentHashMap支持的最多并發(fā)數(shù)是table數(shù)組長度。 get方法源碼get源碼比較簡單,源碼詳解如下圖: get方法的源碼還是非常簡單的,首先根據(jù)hash值找到在table對應處的節(jié)點,然后根據(jù)節(jié)點是紅黑樹還是鏈表查找元素,這里有一個小優(yōu)化是先直接對頭結點進行了比對,有可能直接返回,因為如果hash值設計的好的話是很容易比對上的。 jdk7與jdk8實現(xiàn)比較首先是支持的最大并發(fā)度:jdk7是在初始化的時候指定并且不可變,默認16最大32,而jdk8是針對數(shù)組中每個元素進行同步,所以數(shù)組越大支持的并發(fā)也越大。 線程安全實現(xiàn)方式:jdk7是采用Segment繼承ReentrantLockt通過鎖來實現(xiàn),而jdk8是通過CAS+synchronized來實現(xiàn)。 hash沖突解決方式:jdk7采用的是鏈表,而jdk8采用的是鏈表+紅黑樹的方式,在hash沖突嚴重的情況下jdk8的查找效率會更高。 為什么鏈表長度超過8才轉紅黑樹首先因為紅黑樹中節(jié)點是TreeBin它繼承Node,為了實現(xiàn)紅黑樹TreeBin需要擴展一些功能,所以TreeBin比Node更占空間。 同時遍歷鏈表的平均查找時間復雜度是 O(n),紅黑樹查找的時間復雜度控制在 O(log(n)),在n較小的情況O(n)和 O(log(n)) 差別不大,所以不能直接采用紅黑樹的方式。 所以能不用還是盡量不要紅黑樹,那么至于為什么是8呢? 這在ConcurrentHashMap源碼中有說明,大概意思是如果hash值設置的好分布很均勻的話,鏈表一般不會太長(因為達到容量的0.75就擴容了),如果設置轉紅黑樹的鏈表長度越大轉紅黑樹的概率就越小,通過大量測試當長度為 8 的時候,鏈表轉紅黑樹的概率僅為 0.00000006,所以采用了8。 同時也說明一般來說是很少情況會出現(xiàn)紅黑樹結構的,如果出現(xiàn)了一般情況下我們就要考慮是不是我們設計的對象的hash方法有問題! 總結通過對jdk7與jdk8的ConcurrentHashMap對比學習能夠理解更加深入,面試也會經(jīng)常問這塊的相關知識,總結出來以后感覺就很好回答相關問題了。 Java程序員日常學習筆記,如理解有誤歡迎各位交流討論! |
|
來自: IT樂知 > 《程序員的私房筆記》