今天剖析學習了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 來保證...