ConcurrentHashMap原始碼剖析

2021-09-25 16:11:29 字數 4302 閱讀 6846

鎖優化有乙個很重要的思路,就是拆分鎖的粒度,類似於分布式鎖優化的實踐,把乙份資料拆分為多份資料,對每乙份資料片段來加鎖,這樣就可以提公升多執行緒併發的效率

hashmap底層不就是乙個陣列的資料結構麼?如果你要完全保證裡的併發安全,如果你每次對陣列做一些put、resize、get的操作的時候,你都是加鎖,synchronized好了,此時就會導致併發效能非常的低下

所有的執行緒讀寫hashmap的過程都是序列化的,hashtable,就是採用的這種做法

讀寫鎖,大量的讀鎖和寫鎖衝突的時候,也會導致多執行緒併發的效率大大的降低,也不行。

concurrenthashmap,分段加鎖,把乙份資料拆分為多個segment,對每個段設定一把小鎖,put操作,僅僅只是鎖掉你的那個資料乙個segment而已,鎖一部分的資料,其他的執行緒操作其他segmetn的資料,跟你是沒有競爭的。大大減少了鎖競爭,從而提高了效能。

int hash = spread(key.hashcode());

對key獲取了hashcode,呼叫了spread演算法,獲取到了乙個hash值

static final int spread(int h)

他相當於是把hash值的高低16位都考慮到後面的hash取模演算法裡,這樣就可以把hash值的高低16位的特徵都放到hash取模演算法來運算,有助於盡可能打散各個key在不同的陣列的位置,降低hash衝突的概率

剛開始,table是null的話,此時就要初始化這個table。

u.compareandswapint(this, sizectl, sc, -1):cas操作,sizectl = -1

初始化乙個table陣列,預設的大小就是16

tabat(tab, i = (n - 1) & hash) --》return (node)u.getobjectvolatile(tab, ((long)i << ashift) + abase);

i = (n - 1) & hash,這個就是hash取模的演算法,定位的演算法。定位出來的位置,傳遞給了tabat()函式進行volatile讀。

他在這裡的意思,就是走乙個執行緒安全的操作,unsafe,如果此時陣列那個位置的元素給返回出來。

當前這個陣列這裡沒有元素的話,此時就直接把你的key-value對放在陣列的這個地方了。

通過castabat()--》 u.compareandswapobject(tab, ((long)i << ashift) + abase, c, v);

通過上述的**,使用底層的cas操作,直接將你的key-value對放在了陣列的hash定位到的那個位置。

為什麼concurrenthashmap他說是執行緒安全的呢?

unsafe,cas的操作,依靠執行緒安全的cas對陣列裡的位置進行賦值,只有乙個執行緒可以在這裡成功的將乙個key-value對放在陣列的乙個地方裡。

cas初步體現了分段加鎖的思想???

他並沒有對整個乙個大的陣列進行synchronized加鎖的機制,並沒有,執行緒都會序列化的來執行,效能和效率是很低下的。

多個執行緒同時cas運算元組同乙個位置的元素時,在硬體層面實際上會對這個元素加了乙個鎖,此時只有乙個執行緒可以加鎖成功,其他執行緒則等待他釋放鎖後才可以進行cas操作。陣列中的每乙個位置實際上都可以加一把cas鎖,沒有對乙個陣列全部進行加鎖,僅僅是說對陣列的同乙個位置的元素賦值的時候,多個執行緒會基於cas(隱含式的加鎖),僅僅是對陣列的那個位置的元素進行加鎖而已,隱式的加鎖,硬體層面上的鎖。

陣列大小是16,有16個元素,同時可以允許最多是16個執行緒同時併發的來操作這個陣列,如果16個執行緒操作的是陣列的不同位置的元素的話,此時16個執行緒之間是沒有任何鎖的關係

陣列大小是16,16個元素,cas賦值的機制,實現了乙個效果,這個陣列有16把鎖,每個元素是一把鎖,只有競爭同乙個位置的元素的多個執行緒,才會對一把鎖進行爭用的操作。

如果cas成功,key-value對就直接進到陣列裡去了。

如果cas失敗了以後,兩個或者是多個執行緒同時來進行併發的put操作的時候,如果不巧的定位都到了乙個陣列的元素的位置,此時大家都嘗試進行cas,只有乙個執行緒是可以執行成功的,而其他的執行緒此時cas設定陣列的元素就會失敗。

萬一有執行緒cas失敗了呢?此時會就什麼都不會幹,直接進入下一輪for迴圈,(f = tabat(tab, i = (n - 1) & hash)) == null,此時會發現說陣列的那個位置的元素已經不是null了,因為之前已經有人在陣列的那個位置設定了乙個node。此時就應該對陣列的那個位置的元素進行鍊錶+紅黑樹的處理,把衝突的多個key-value對掛成乙個鍊錶或者是紅黑樹。

f就代表了陣列當前位置的元素,node節點

synchronized(f)

這個就是所謂的jdk 1.8裡的concurrenthashmap分段加鎖的思想,淋漓盡致的體現,他僅僅就是對陣列這個位置的元素加了一把鎖而已。同一時間只有乙個執行緒可以進鎖來進行這個位置的鍊錶+紅黑樹的處理,這個就是分段加鎖

陣列的中的每乙個元素,無論是null情況下來賦值(cas,分段加鎖思想),有值情況下來處理鍊錶/紅黑樹(synchronized,對元素本身加鎖,更加是分段加鎖思想),都是對陣列的乙個元素加鎖而已。你的陣列有多大,有多少個元素,你就有多少把鎖,大幅度的提公升了整個hashmap加鎖的效率。

如果有乙個執行緒成功對陣列的某個元素加鎖了,synchronized.如何對這個位置的元素進行鍊錶/紅黑樹的處理?

if (e.hash == hash &&

((ek = e.key) == key ||

(ek != null && key.equals(ek))))

如果發現說我當前要put的key跟陣列裡這個位置的key是一樣的,此時就對陣列當前位置的元素的value值覆蓋一下

如果兩個key不同,就預設走鍊錶的乙個處理,此時就是把e.next = 新封裝的乙個節點,如下**所示,e就是陣列當前位置的元素

nodepred = e;

if ((e = e.next) == null)

if (bincount != 0)

上面這塊**的判斷就是,如果乙個鍊錶的元素的數量超過了8,達到了乙個閾值之後,就會將鍊錶轉換為紅黑樹。如果轉換為紅黑樹以後,下次如果有hash衝突的問題,是直接把key-value對加入到紅黑樹裡去   

else if (f instanceof treebin)

}出現hash衝突的時候,分段加鎖成功了以後,就會做值覆蓋/鍊錶/紅黑樹處理,前提條件,都是你對陣列當前位置的元素synchronized加鎖成功了才可以的。

不加鎖,但是他通過volatile讀,盡可能給你保證說是讀到了其他執行緒修改的乙個最新的值,但是不需要加鎖。

volatile底層硬體級別的原理,volatile讀操作的話,會插入乙個load屏障,絕對是在讀取資料的時候,一定會嗅探一下,探查一下無效佇列,這個資料是否被別人修改過了。

此時必須立馬過期掉本地快取記憶體裡的快取資料,invalid(i),然後再讀的時候,就需要傳送read訊息到匯流排,從其他執行緒修改修改這個值的執行緒的快取記憶體裡(或者從主存讀。具體從**讀取最新的值,跟cpu有關係),必須這個載入到最新的值

size方法,是幫助你去讀到當前最新的乙份資料,通過volatile的讀操作。但是因為讀操作是不加鎖,他不定根可以保證說,你讀的時候就一定沒人修改了,很可能是你剛剛讀完乙份資料,就有人來修改

這個資料結構,記錄了陣列的每個位置掛載了多少個元素,每個位置都可能掛的是鍊錶或者是紅黑樹,此時可能乙個位置有多個元素。注意:value值是volatile變數,保證可見性。

將每個位置對應的countercell累加起來,再加上basecount就是我們map的總大小。

jdk 1.7的concurrenthashmap,稍微是有一些差距的。segment,16個segment,16個分段,每個segment對應node陣列,每個segment有一把鎖,也就是說對乙個segment裡的node陣列的不同的元素如果要put操作的話,其實都是要競爭乙個鎖,序列化來處理的。鎖的粒度是比較粗的,因為乙個node陣列是一把鎖,但是他有多個node陣列

jdk 1.8的concurrenthashmap,優化了,鎖粒度細化,他是就乙個node陣列,正常會擴容的,但是他的鎖粒度是針對的陣列裡的每個元素,每個元素的處理會加一把鎖,不同的元素就會有不同的鎖。大幅度的提公升了多執行緒併發寫concurrenthashmap的效能,降低了鎖的衝突。

ConcurrentHashMap原始碼分析

hashmap 先說hashmap,hashmap是執行緒不安全 的,在併發環境下,可能會形成環狀鍊錶 hashtable hashtable和hashmap的實現原理幾乎一樣,差別無非是1.hashtable不允許key和value為null 2.hashtable是執行緒安全的。但是hashta...

ConcurrentHashMap原始碼詳解

成員變數private static final int maximum capacity 1 30 private static final int default capacity 16 static final int max array size integer.max value 8 pr...

concurrentHashMap原始碼分析

concurrenthashmap是hashmap的執行緒安全版本,內部也是使用 陣列 鍊錶 紅黑樹 的結構來儲存元素。相比於同樣執行緒安全的hashtable來說,效率等各方面都有極大地提高。在這裡可以使用上篇那個 進行測試,根據結果可以知道concurrenthashmap是執行緒安全的,由於分...