ConcurrentHashMap學習筆記

2021-10-02 20:12:51 字數 3999 閱讀 6401

今天剖析學習了concurrenthashmap,總結下筆記

我們從類注釋上大概可以得到如下資訊:

1.所有的操作都是執行緒安全的,我們在使用時,無需再加鎖;

2.多個執行緒同時進行 put、remove 等操作時並不會阻塞,可以同時進行,和 hashtable 不同,hashtable 在操作時,會鎖住整個 map;

3.迭代過程中,即使 map 結構被修改,也不會拋concurrentmodificationexception 異常;

4.除了陣列 + 鍊錶 + 紅黑樹的基本結構外,新增了轉移節點,是為了保證擴容時的執行緒安全的節點;

concurrenthashmap 和 hashmap 兩者的相同之處:

1.陣列、鍊錶結構幾乎相同,所以底層對資料結構的操作思路是相同的

2.都實現了 map 介面,繼承了 abstractmap 抽象類,所以大多數的方法也都是相同的,hashmap 有的方法,concurrenthashmap 幾乎都有,所以當我們需要從 hashmap 切換到 concurrenthashmap 時,無需關心兩者之間的相容問題。

不同之處:

1.紅黑樹結構略有不同,hashmap 的紅黑樹中的節點叫做 treenode,treenode 不僅僅有屬性,還維護著紅黑樹的結構,比如說查詢,新增等等;concurrenthashmap 中紅黑樹被拆分成兩塊,treenode 僅僅維護的屬性和查詢功能,新增了 treebin,來維護紅黑樹結構,並負責根節點的加鎖和解鎖;

2.新增 forwardingnode **移)節點,擴容的時候會使用到,通過使用該節點,來保證擴容時的執行緒安全。

put方法

原始碼如下

public v put

(k key, v value)

/** implementation for put and putifabsent */

final v putval

(k key, v value,

boolean onlyifabsent)

elseif(

(fh = f.hash)

== moved)

tab =

helptransfer

(tab, f)

;else

node

pred = e;if(

(e = e.next)

== null)}}

else

if(f instanceof

treebin)}

}}if(bincount !=0)

}}addcount

(1l, bincount)

;return null;

}

大概步驟為:

1.如果陣列為空,初始化;

2.計算當前位置有沒有值,沒有值的話,使用cas 建立,失敗繼續自旋(for 死迴圈),直到成功。

3.如果位置是轉移節點(正在擴容),就會一直自旋等待擴容完成之後再新增

4.當前位置有值的,先鎖定當前位置,保證其餘執行緒不能操作,如果是鍊錶,新增值到鍊錶的尾部,如果是紅黑樹,使用紅黑樹新增的方法新增;

5.新增完成之後 檢查 需不需要擴容,需要的話去擴容。

看完最主要的put,新增完後需要檢查擴容,那麼如何擴容呢?

concurrenthashmap 擴容的方法叫做 transfer

原始碼如下:

private

final

void

transfer

(node

tab, node

nexttab)

catch

(throwable ex)

nexttable = nexttab;

transferindex = n;

}int nextn = nexttab.length;

forwardingnode

fwd =

newforwardingnode

(nexttab)

;boolean advance =

true

;boolean finishing =

false

;// to ensure sweep before committing nexttab

for(

int i =

0, bound =0;

;)else

if(u.compareandswapint

(this

, transferindex, nextindex,

nextbound =

(nextindex > stride ?

nextindex - stride :0)

))}if

(i <

0|| i >= n || i + n >= nextn)

if(u.

compareandswapint

(this

, sizectl, sc = sizectl, sc -1)

)}elseif(

(f =

tabat

(tab, i)

)== null)

advance =

castabat

(tab, i, null, fwd)

;elseif(

(fh = f.hash)

== moved)

advance =

true

;// already processed

else}if

(runbit ==0)

else

for(node

p = f; p != lastrun; p = p.next)

settabat

(nexttab, i, ln)

;settabat

(nexttab, i + n, hn)

;settabat

(tab, i, fwd)

; advance =

true;}

else

if(f instanceof

treebin

)else

} ln =

(lc <= untreeify_threshold)

?untreeify

(lo)

:(hc !=0)

?new

treebin

(lo)

: t;

hn =

(hc <= untreeify_threshold)

?untreeify

(hi)

:(lc !=0)

?new

treebin

(hi)

: t;

settabat

(nexttab, i, ln)

;settabat

(nexttab, i + n, hn)

;settabat

(tab, i, fwd)

; advance =

true;}

}}}}

}

大概思路總結如下:

1.首先需要把老陣列的值全部拷貝到擴容之後的新陣列上,先從陣列的隊尾開始拷貝;

2.拷貝陣列的槽點時,先把原陣列槽點鎖住,保證原陣列槽點不能操作,成功拷貝到新陣列時,把原陣列槽點賦值為轉移節點;

3.這時如果有新資料正好需要 put 到此槽點時,發現槽點為轉移節點,就會一直等待,所以在擴容完成之前,該槽點對應的資料是不會發生變化的;

4.從陣列的尾部拷貝到頭部,每拷貝成功一次,就把原陣列中的節點設定成轉移節點;

5.直到所有陣列資料都拷貝到新陣列時,直接把新陣列整個賦值給陣列容器,拷貝完成。

學習ConcurrentHashMap併發寫機制

上篇文章講了 unsafe 類中 cas 的實現,其實是在為這篇文章打基礎。不太熟悉的小夥伴請移步unsafe 中 cas 的實現。本篇文章主要基於openjdk8來做原始碼解析。concurrenthashmap 基於 hashmap 實現。jdk1.7 和 jdk1.8 作為併發容器在實現上是有...

ConcurrentHashMap 併發雜湊對映

如執行緒1使用put進行新增元素,執行緒2不但不能使用put方法新增元素,並且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。圖1 1 hashtable中在put 方法上加鎖 hashtable容器在競爭激烈的併發環境下表現出效率低下的原因,是因為所有訪問hashtable的執行緒都必須競...

ConcurrentHashMap 原理簡要分析

在之前寫過hashtable 與hashmap 兩者之間的異同 通過前面文章,可以知道hashmap 中未進行同步考慮,而 hashtable 則使用了 synchronized 帶來的直接影響就是可選擇,我們可以在單執行緒時使用 hashmap 提高效率,而多執行緒時用 hashtable 來保證...