從原始碼來窺其一斑!
我們都知道hashMap不是執行緒安全的,因為在擴容方法中很容易出現死迴圈,hashTable使用鎖的方式比較簡單暴力,幾乎在所有操作方法上都加了synchronized鎖,導致總體效能很差,concurrentHashmap憑藉執行緒安全且效能優異一直都是高併發中的首選key-value型資料結構;
concurrentHashmap的高效能有以下原因:
一,分段鎖:jdk8中對concurrentHashmap進行了改進,拋棄了jdk7中新建segment作為分段鎖的過程,jdk8中雖沿用了這種分段鎖的思想,卻直接使用陣列中的資料作為分段鎖保證concurrentHashmap在上鎖的時候只針對陣列下標下的資料進行上鎖(比如如果陣列長度為256,那麼每次put平均只有1/256的資料被鎖),而大多數其他的資料還是能進行正常的增刪改操作,無需阻塞等待,這無疑極大的降低了鎖的粒度,提升了效能。
二,紅黑樹 :jdk8中引入了紅黑樹結構,在單個數組下標內的資料達到8以後,會自動轉換為紅黑樹進行儲存,使用大O表示法表示效率的話,紅黑樹的查詢效率為O(log(n)),而連結串列的效率為O(n),當資料量越來越大的時候,紅黑樹的效率明顯好於連結串列,所以concurrentHashmap效能得到很大提升;
現在我們主要從put方法中的主要方法來分析效能的提升:
spread(key.hashCode());//作用是再次雜湊,減少衝突 ,原始碼如下
其中涉及到的位運算有
>>> 16:無符號右移16位,空位以0補齊 。
^:異或運算子-->相同為0,不同為1; &:與運算子-->全1得1,否則0;
(h ^ (h >>> 16)) & HASH_BITS; 所以這句程式碼的意思就是不僅消除高16位的影響,同時獲得正整數的hash值
再來看後面的方法, 如上圖:
1,就是判斷當這個hash表還是空的時候,呼叫initTable進行初始化; 2,使用(n - 1) & hash)計算陣列下標,如果資料指定下標處為null,則直接插入,注:cas是java8中的concurrentHashmap引入的執行緒安全判斷,CAS演算法做為樂觀鎖;
3,(fh = f.hash) == MOVED,走到此處說明下標內有node,且該node的值為-1(MODED=-1),搜尋全類發現MODED是在呼叫有參構造器ForwardingNode中預設寫入的,而這個呼叫處剛好在transfer方法中,所以我們推斷,擴容的時候先將陣列下標內的node.hash置為-1! 同時在3這一步中呼叫helpTransfer(tab, f)參與擴容,並把資料寫入;
4,走到這說明node不是空的,也沒在擴容,那麼鎖住該下標下的node,並把新value插入連結串列中; 5,如果鎖住的這個node能例項化為TreeBin,則代表已經轉化為紅黑樹進行儲存,將資料插入紅黑樹中; 6,判斷在4,5中計算得到的陣列下標內所有節點總數,如果滿足轉化為紅黑樹的條件(節點數大於8),則自動轉化為紅黑樹進行儲存!
總的來說,concurrentHashmap之所以效能高就是因為使用了分段鎖和紅黑樹!
至於conrrentHashmap其他的方法的原始碼分析,後期會補上的,更多的技術分享,敬請關注!
從原始碼來窺其一斑!
我們都知道hashMap不是執行緒安全的,因為在擴容方法中很容易出現死迴圈,hashTable使用鎖的方式比較簡單暴力,幾乎在所有操作方法上都加了synchronized鎖,導致總體效能很差,concurrentHashmap憑藉執行緒安全且效能優異一直都是高併發中的首選key-value型資料結構;
concurrentHashmap的高效能有以下原因:
一,分段鎖:jdk8中對concurrentHashmap進行了改進,拋棄了jdk7中新建segment作為分段鎖的過程,jdk8中雖沿用了這種分段鎖的思想,卻直接使用陣列中的資料作為分段鎖保證concurrentHashmap在上鎖的時候只針對陣列下標下的資料進行上鎖(比如如果陣列長度為256,那麼每次put平均只有1/256的資料被鎖),而大多數其他的資料還是能進行正常的增刪改操作,無需阻塞等待,這無疑極大的降低了鎖的粒度,提升了效能。
二,紅黑樹 :jdk8中引入了紅黑樹結構,在單個數組下標內的資料達到8以後,會自動轉換為紅黑樹進行儲存,使用大O表示法表示效率的話,紅黑樹的查詢效率為O(log(n)),而連結串列的效率為O(n),當資料量越來越大的時候,紅黑樹的效率明顯好於連結串列,所以concurrentHashmap效能得到很大提升;
現在我們主要從put方法中的主要方法來分析效能的提升:
spread(key.hashCode());//作用是再次雜湊,減少衝突 ,原始碼如下
其中涉及到的位運算有
>>> 16:無符號右移16位,空位以0補齊 。
^:異或運算子-->相同為0,不同為1; &:與運算子-->全1得1,否則0;
(h ^ (h >>> 16)) & HASH_BITS; 所以這句程式碼的意思就是不僅消除高16位的影響,同時獲得正整數的hash值
再來看後面的方法, 如上圖:
1,就是判斷當這個hash表還是空的時候,呼叫initTable進行初始化; 2,使用(n - 1) & hash)計算陣列下標,如果資料指定下標處為null,則直接插入,注:cas是java8中的concurrentHashmap引入的執行緒安全判斷,CAS演算法做為樂觀鎖;
3,(fh = f.hash) == MOVED,走到此處說明下標內有node,且該node的值為-1(MODED=-1),搜尋全類發現MODED是在呼叫有參構造器ForwardingNode中預設寫入的,而這個呼叫處剛好在transfer方法中,所以我們推斷,擴容的時候先將陣列下標內的node.hash置為-1! 同時在3這一步中呼叫helpTransfer(tab, f)參與擴容,並把資料寫入;
4,走到這說明node不是空的,也沒在擴容,那麼鎖住該下標下的node,並把新value插入連結串列中; 5,如果鎖住的這個node能例項化為TreeBin,則代表已經轉化為紅黑樹進行儲存,將資料插入紅黑樹中; 6,判斷在4,5中計算得到的陣列下標內所有節點總數,如果滿足轉化為紅黑樹的條件(節點數大於8),則自動轉化為紅黑樹進行儲存!
總的來說,concurrentHashmap之所以效能高就是因為使用了分段鎖和紅黑樹!
至於conrrentHashmap其他的方法的原始碼分析,後期會補上的,更多的技術分享,敬請關注!