文章
http://www.reibang.com/p/289cf670733e 猜煮,主要講 JDK 8
http://www.reibang.com/p/487d00afe6ca 彰阴, 將擴(kuò)容操作
http://www.reibang.com/p/7db1ac8395ee 脓诡, 1.7 匪凉, 1.8 都有講
在 1.8 版本以前,ConcurrentHashMap 采用分段鎖的概念剪勿,使鎖更加細(xì)化撞芍,但是 1.8 已經(jīng)改變了這種思路,而是利用 CAS + Synchronized 來保證并發(fā)更新的安全翩瓜,當(dāng)然底層采用數(shù)組 + 鏈表 + 紅黑樹的存儲(chǔ)結(jié)構(gòu)受扳。
數(shù)據(jù)結(jié)構(gòu)
重要字段
/**
* races. Updated via CAS.
* 記錄容器的容量大小,通過CAS更新
*/
private transient volatile long baseCount;
/**
* 這個(gè)sizeCtl是volatile的兔跌,那么他是線程可見的勘高,一個(gè)思考:它是所有修改都在CAS中進(jìn)行,但是sizeCtl為什么不設(shè)計(jì)成LongAdder(jdk8出現(xiàn)的)類型呢坟桅?
* 或者設(shè)計(jì)成AtomicLong(在高并發(fā)的情況下比LongAdder低效)华望,這樣就能減少自己操作CAS了。
*
* 來看下注釋仅乓,當(dāng)sizeCtl小于0說明有多個(gè)線程正則等待擴(kuò)容結(jié)果赖舟,參考transfer函數(shù)
*
* sizeCtl等于0是默認(rèn)值,大于0是擴(kuò)容的閥值
*/
private transient volatile int sizeCtl;
/**
* 自旋鎖 (鎖定通過 CAS) 在調(diào)整大小和/或創(chuàng)建 CounterCells 時(shí)使用夸楣。 在CounterCell類更新value中會(huì)使用宾抓,功能類似顯示鎖和內(nèi)置鎖,性能更好
* 在Striped64類也有應(yīng)用
*/
private transient volatile int cellsBusy;
還有最重要的節(jié)點(diǎn)類Node裕偿,注意val和next是volatile類型
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
下面對(duì)重點(diǎn)的常量進(jìn)行說明:
table:用來存放Node節(jié)點(diǎn)數(shù)據(jù)的洞慎,默認(rèn)為null,默認(rèn)大小為16的數(shù)組嘿棘,每次擴(kuò)容時(shí)大小總是2的冪次方
nextTable:擴(kuò)容時(shí)新生成的數(shù)據(jù),數(shù)組為table的兩倍
Node:節(jié)點(diǎn)旭绒,保存key-value的數(shù)據(jù)結(jié)構(gòu)
ForwardingNode:一個(gè)特殊的Node節(jié)點(diǎn)鸟妙,hash值為-1焦人,其中存儲(chǔ)nextTable的引用。只有table發(fā)生擴(kuò)容的時(shí)候重父,F(xiàn)orwardingNode才會(huì)發(fā)揮作用花椭,作為一個(gè)占位符放在table中表示當(dāng)前節(jié)點(diǎn)為null或則已經(jīng)被移動(dòng)
-
sizeCtl:控制標(biāo)識(shí)符,用來控制table初始化和擴(kuò)容操作的房午,在不同的地方有不同的用途矿辽,其值也不同,所代表的含義也不同
負(fù)數(shù)代表正在進(jìn)行初始化或擴(kuò)容操作
-1代表正在初始化
-N 表示有N-1個(gè)線程正在進(jìn)行擴(kuò)容操作
正數(shù)或0代表hash表還沒有被初始化郭厌,這個(gè)數(shù)值表示初始化或下一次進(jìn)行擴(kuò)容的大小
重點(diǎn)內(nèi)部類
-
Node
Node 存儲(chǔ) key-value 鍵值對(duì)袋倔,所有插入ConcurrentHashMap的中數(shù)據(jù)都將會(huì)包裝在Node中。
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; //帶有volatile折柠,保證可見性 volatile Node<K,V> next; //下一個(gè)節(jié)點(diǎn)的指針 /** 不允許修改value的值 */ public final V setValue(V value) { throw new UnsupportedOperationException(); } /** 賦值get()方法 */ Node<K,V> find(int h, Object k) { Node<K,V> e = this; if (k != null) { do { K ek; if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; } while ((e = e.next) != null); } return null; } }
在Node內(nèi)部類中宾娜,其屬性value、next都是帶有volatile的扇售。同時(shí)其對(duì)value的setter方法進(jìn)行了特殊處理前塔,不允許直接調(diào)用其setter方法來修改value的值。最后Node還提供了find方法來賦值map.get()承冰。
-
TreeNode
在 1.8 的 ConcurrentHashMap 中华弓,如果鏈表的數(shù)據(jù)過長是會(huì)轉(zhuǎn)換為紅黑樹來處理。當(dāng)它并不是直接轉(zhuǎn)換困乒,而是將這些鏈表的節(jié)點(diǎn)包裝成TreeNode放在TreeBin對(duì)象中寂屏,然后由TreeBin完成紅黑樹的轉(zhuǎn)換。所以TreeNode也必須是ConcurrentHashMap的一個(gè)核心類顶燕,其為樹節(jié)點(diǎn)類凑保。源碼展示TreeNode繼承Node,且提供了findTreeNode用來查找查找hash為h涌攻,key為k的節(jié)點(diǎn)欧引。
-
TreeBin
該類并不負(fù)責(zé)key-value的鍵值對(duì)包裝,它用于在鏈表轉(zhuǎn)換為紅黑樹時(shí)包裝TreeNode節(jié)點(diǎn)恳谎,也就是說ConcurrentHashMap紅黑樹存放是TreeBin芝此,不是TreeNode。該類封裝了一系列的方法因痛,包括putTreeVal婚苹、lookRoot、UNlookRoot鸵膏、remove膊升、balanceInsetion、balanceDeletion谭企。
-
ForwardingNode
這是一個(gè)真正的輔助類廓译,該類僅僅只存活在ConcurrentHashMap擴(kuò)容操作時(shí)评肆。只是一個(gè)標(biāo)志節(jié)點(diǎn),并且指向nextTable非区,它提供find方法而已瓜挽。該類也是集成Node節(jié)點(diǎn),其hash為-1征绸,key久橙、value、next均為null管怠。
構(gòu)造函數(shù)
初始化: initTable()
ConcurrentHashMap的初始化主要由initTable()方法實(shí)現(xiàn)淆衷,在上面的構(gòu)造函數(shù)中我們可以看到,其實(shí)ConcurrentHashMap在構(gòu)造函數(shù)中并沒有做什么事排惨,僅僅只是設(shè)置了一些參數(shù)而已吭敢。其真正的初始化是發(fā)生在插入的時(shí)候,例如put暮芭、merge鹿驼、compute、computeIfAbsent辕宏、computeIfPresent操作時(shí)畜晰。其方法定義如下:
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//sizeCtl < 0 表示有其他線程在初始化,該線程必須掛起
if ((sc = sizeCtl) < 0)
Thread.yield();
// 如果該線程獲取了初始化的權(quán)利瑞筐,則用CAS將sizeCtl設(shè)置為-1凄鼻,表示本線程正在初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
// 進(jìn)行初始化
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 下次擴(kuò)容的大小
sc = n - (n >>> 2); ///相當(dāng)于0.75*n 設(shè)置一個(gè)擴(kuò)容的閾值
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
初始化方法initTable()的關(guān)鍵就在于sizeCtl,該值默認(rèn)為0聚假,如果在構(gòu)造函數(shù)時(shí)有參數(shù)傳入該值則為2的冪次方块蚌。該值如果 < 0,表示有其他線程正在初始化膘格,則必須暫停該線程峭范。如果線程獲得了初始化的權(quán)限則先將sizeCtl設(shè)置為-1,防止有其他線程進(jìn)入瘪贱,最后將sizeCtl設(shè)置0.75 * n纱控,表示擴(kuò)容的閾值。
put
核心思想依然是根據(jù)hash值計(jì)算節(jié)點(diǎn)插入在table的位置菜秦,如果該位置為空甜害,則直接插入,否則插入到鏈表或者樹中球昨。但是ConcurrentHashMap會(huì)涉及到多線程情況就會(huì)復(fù)雜很多尔店。
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//key、value均不能為null
if (key == null || value == null) throw new NullPointerException();
//計(jì)算hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// table為null,進(jìn)行初始化工作
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//如果i位置沒有節(jié)點(diǎn)闹获,則直接插入期犬,不需要加鎖
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 有線程正在進(jìn)行擴(kuò)容操作河哑,則先幫助擴(kuò)容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//對(duì)該節(jié)點(diǎn)進(jìn)行加鎖處理(hash值相同的鏈表的頭節(jié)點(diǎn))避诽,對(duì)性能有點(diǎn)兒影響
synchronized (f) {
if (tabAt(tab, i) == f) {
//fh > 0 表示為鏈表,將該節(jié)點(diǎn)插入到鏈表尾部
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//hash 和 key 都一樣璃谨,替換value
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
//putIfAbsent()
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
//鏈表尾部 直接插入
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//樹節(jié)點(diǎn)沙庐,按照樹的插入操作進(jìn)行插入
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// 如果鏈表長度已經(jīng)達(dá)到臨界值8 就需要把鏈表轉(zhuǎn)換為樹結(jié)構(gòu)
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//size + 1
addCount(1L, binCount);
return null;
}
按照上面的源碼,我們可以確定put整個(gè)流程如下:
判空佳吞;ConcurrentHashMap的key拱雏、value都不允許為null
計(jì)算hash。利用方法計(jì)算hash值底扳。
-
遍歷table铸抑,進(jìn)行節(jié)點(diǎn)插入操作,過程如下:
如果table為空衷模,則表示ConcurrentHashMap還沒有初始化鹊汛,則進(jìn)行初始化操作:initTable()
根據(jù)hash值獲取節(jié)點(diǎn)的位置i,若該位置為空阱冶,則直接插入刁憋,這個(gè)過程是不需要加鎖的。計(jì)算f位置:i=(n - 1) & hash
如果檢測到fh = f.hash == -1木蹬,則f是ForwardingNode節(jié)點(diǎn)至耻,表示有其他線程正在進(jìn)行擴(kuò)容操作,則幫助線程一起進(jìn)行擴(kuò)容操作
如果f.hash >= 0 表示是鏈表結(jié)構(gòu)镊叁,則遍歷鏈表尘颓,如果存在當(dāng)前key節(jié)點(diǎn)則替換value,否則插入到鏈表尾部晦譬。如果f是TreeBin類型節(jié)點(diǎn)疤苹,則按照紅黑樹的方法更新或者增加節(jié)點(diǎn)
若鏈表長度 > TREEIFY_THRESHOLD(默認(rèn)是8),則將鏈表轉(zhuǎn)換為紅黑樹結(jié)構(gòu), 此時(shí)還需要進(jìn)一步判斷蛔添,并不是直接轉(zhuǎn)換的(當(dāng)數(shù)組大小已經(jīng)超過64并且鏈表中的元素個(gè)數(shù)超過默認(rèn)設(shè)定(8個(gè))時(shí)痰催,將鏈表轉(zhuǎn)化為紅黑樹)
調(diào)用addCount方法,ConcurrentHashMap的size + 1迎瞧,此方法還會(huì)判斷是否需要擴(kuò)容夸溶。
這里整個(gè)put操作已經(jīng)完成。
get
get操作的整個(gè)邏輯非常清楚: - 計(jì)算hash值 - 判斷table是否為空凶硅,如果為空缝裁,直接返回null - 根據(jù)hash值獲取table中的Node節(jié)點(diǎn)(tabAt(tab, (n - 1) & h)),然后根據(jù)鏈表或者樹形方式找到相對(duì)應(yīng)的節(jié)點(diǎn),返回其value值捷绑。
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 計(jì)算hash
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 搜索到的節(jié)點(diǎn)key與傳入的key相同且不為null,直接返回這個(gè)節(jié)點(diǎn)
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 樹
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 鏈表韩脑,遍歷
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
remove
size
ConcurrentHashMap的size()方法我們雖然用得不是很多,但是我們還是很有必要去了解的粹污。ConcurrentHashMap的size()方法返回的是一個(gè)不精確的值段多,因?yàn)樵谶M(jìn)行統(tǒng)計(jì)的時(shí)候有其他線程正在進(jìn)行插入和刪除操作。當(dāng)然為了這個(gè)不精確的值壮吩,ConcurrentHashMap也是操碎了心进苍。
為了更好地統(tǒng)計(jì)size,ConcurrentHashMap提供了baseCount鸭叙、counterCells兩個(gè)輔助變量和一個(gè)CounterCell輔助內(nèi)部類觉啊。
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
//ConcurrentHashMap中元素個(gè)數(shù),但返回的不一定是當(dāng)前Map的真實(shí)元素個(gè)數(shù)∩虮矗基于CAS無鎖更新
private transient volatile long baseCount;
private transient volatile CounterCell[] counterCells;
size()方法定義如下:
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
內(nèi)部調(diào)用sunmCount():
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
//遍歷杠人,所有counter求和
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
sumCount()就是迭代counterCells來統(tǒng)計(jì)sum的過程。我們知道put操作時(shí)宋下,肯定會(huì)影響size()嗡善,我們就來看看CouncurrentHashMap是如何為了這個(gè)不和諧的size()操碎了心。
在put()方法最后會(huì)調(diào)用addCount()方法杨凑,該方法主要做兩件事滤奈,一件更新baseCount的值,第二件檢測是否進(jìn)行擴(kuò)容撩满,我們只看更新baseCount部分:
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// s = b + x蜒程,完成baseCount++操作;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 多線程CAS發(fā)生失敗時(shí)執(zhí)行
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
// 檢查是否進(jìn)行擴(kuò)容
}
x == 1伺帘,如果counterCells == null
昭躺,則U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)
耗美,如果并發(fā)競爭比較大可能會(huì)導(dǎo)致改過程失敗董习,如果失敗則最終會(huì)調(diào)用 fullAddCount() 方法。
其實(shí)為了提高高并發(fā)的時(shí)候 baseCount 可見性的失敗問題遗增,又避免一直重試张咳,JDK 8 引入了類 Striped64,其中 LongAdder 和 DoubleAdder 都是基于該類實(shí)現(xiàn)的帝洪,而 CounterCell 也是基于 Striped64 實(shí)現(xiàn)的。如果counterCells 脚猾!= null葱峡,且uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)
也失敗了,同樣會(huì)調(diào)用 fullAddCount() 方法龙助,最后調(diào)用 sumCount() 計(jì)算 s砰奕。
其實(shí)在1.8中,它不推薦 size() 方法,而是推崇 mappingCount()
方法军援,該方法的定義和 size() 方法基本一致:
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
擴(kuò)容
什么時(shí)候擴(kuò)容
當(dāng)前容量超過閾值
當(dāng)鏈表中元素個(gè)數(shù)超過默認(rèn)設(shè)定(8個(gè))仅淑,當(dāng)數(shù)組的大小還未超過64的時(shí)候,此時(shí)進(jìn)行數(shù)組的擴(kuò)容胸哥,如果超過則將鏈表轉(zhuǎn)化成紅黑樹
其他線程在擴(kuò)容時(shí)幫助擴(kuò)容
擴(kuò)容相關(guān)的屬性
采用多線程擴(kuò)容涯竟。整個(gè)擴(kuò)容過程,通過CAS設(shè)置sizeCtl烘嘱,transferIndex等變量協(xié)調(diào)多個(gè)線程進(jìn)行并發(fā)擴(kuò)容昆禽。
nextTable
擴(kuò)容期間,將table數(shù)組中的元素 遷移到 nextTable蝇庭。
sizeCtl屬性
多線程之間,以volatile的方式讀取sizeCtl屬性捡硅,來判斷ConcurrentHashMap當(dāng)前所處的狀態(tài)哮内。通過cas設(shè)置sizeCtl屬性,告知其他線程ConcurrentHashMap的狀態(tài)變更壮韭。
不同狀態(tài)北发,sizeCtl所代表的含義也有所不同。
未初始化:
sizeCtl=0:表示沒有指定初始容量喷屋。
sizeCtl>0:表示初始容量琳拨。
初始化中:
sizeCtl=-1,標(biāo)記作用,告知其他線程屯曹,正在初始化
正常狀態(tài):
sizeCtl=0.75n ,擴(kuò)容閾值
擴(kuò)容中:
sizeCtl < 0 : 表示有其他線程正在執(zhí)行擴(kuò)容
sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此時(shí)只有一個(gè)線程在執(zhí)行擴(kuò)容
transferIndex屬性
private transient volatile int transferIndex;
/**
擴(kuò)容線程每次最少要遷移16個(gè)hash桶
*/
private static final int MIN_TRANSFER_STRIDE = 16;
擴(kuò)容索引狱庇,表示已經(jīng)分配給擴(kuò)容線程的table數(shù)組索引位置。主要用來協(xié)調(diào)多個(gè)線程恶耽,并發(fā)安全地獲取遷移任務(wù)(hash桶)密任。
ForwardingNode節(jié)點(diǎn)
標(biāo)記作用,表示其他線程正在擴(kuò)容偷俭,并且此節(jié)點(diǎn)已經(jīng)擴(kuò)容完畢
關(guān)聯(lián)了nextTable,擴(kuò)容期間可以通過find方法浪讳,訪問已經(jīng)遷移到了nextTable中的數(shù)據(jù)
具體實(shí)現(xiàn)
- 在擴(kuò)容之前,transferIndex 在數(shù)組的最右邊 涌萤。此時(shí)有一個(gè)線程發(fā)現(xiàn)已經(jīng)到達(dá)擴(kuò)容閾值淹遵,準(zhǔn)備開始擴(kuò)容。
2 擴(kuò)容線程负溪,在遷移數(shù)據(jù)之前透揣,首先要將transferIndex左移(以cas的方式修改 transferIndex=transferIndex-stride
(要遷移hash桶的個(gè)數(shù))),獲取遷移任務(wù)笙以。每個(gè)擴(kuò)容線程都會(huì)通過for循環(huán)+CAS的方式設(shè)置transferIndex淌实,因此可以確保多線程擴(kuò)容的并發(fā)安全。
換個(gè)角度,我們可以將待遷移的table數(shù)組拆祈,看成一個(gè)任務(wù)隊(duì)列恨闪,transferIndex看成任務(wù)隊(duì)列的頭指針。而擴(kuò)容線程放坏,就是這個(gè)隊(duì)列的消費(fèi)者咙咽。擴(kuò)容線程通過CAS設(shè)置transferIndex索引的過程,就是消費(fèi)者從任務(wù)隊(duì)列中獲取任務(wù)的過程淤年。為了性能考慮钧敞,我們當(dāng)然不會(huì)每次只獲取一個(gè)任務(wù)(hash桶),因此ConcurrentHashMap規(guī)定麸粮,每次至少要獲取16個(gè)遷移任務(wù)(遷移16個(gè)hash桶溉苛,MIN_TRANSFER_STRIDE = 16)
cas設(shè)置transferIndex的源碼如下:
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//計(jì)算每次遷移的node個(gè)數(shù)
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // 確保每次遷移的node個(gè)數(shù)不少于16個(gè)
...
for (int i = 0, bound = 0;;) {
...
//cas無鎖算法設(shè)置 transferIndex = transferIndex - stride
if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
...
...
}
...//省略遷移邏輯
}
}
過程分析
- 線程執(zhí)行put操作,發(fā)現(xiàn)容量已經(jīng)達(dá)到擴(kuò)容閾值弄诲,需要進(jìn)行擴(kuò)容操作愚战,此時(shí)transferindex=tab.length=32
- 擴(kuò)容線程A 以cas的方式修改transferindex=31-16=16 ,然后按照降序遷移table[31]--table[16]這個(gè)區(qū)間的hash桶
- 遷移hash桶時(shí),會(huì)將桶內(nèi)的鏈表或者紅黑樹齐遵,按照一定算法寂玲,拆分成2份,將其插入nextTable[i]和nextTable[i+n](n是table數(shù)組的長度)梗摇。 遷移完畢的hash桶,會(huì)被設(shè)置成ForwardingNode節(jié)點(diǎn)拓哟,以此告知訪問此桶的其他線程,此節(jié)點(diǎn)已經(jīng)遷移完畢伶授。
- 此時(shí)線程2訪問到了ForwardingNode節(jié)點(diǎn)断序,如果線程2執(zhí)行的put或remove等寫操作,那么就會(huì)先幫其擴(kuò)容谎砾。如果線程2執(zhí)行的是get等讀方法逢倍,則會(huì)調(diào)用ForwardingNode的find方法,去nextTable里面查找相關(guān)元素景图。
- 線程2加入擴(kuò)容操作
-
如果準(zhǔn)備加入擴(kuò)容的線程较雕,發(fā)現(xiàn)以下情況,放棄擴(kuò)容挚币,直接返回亮蒋。
發(fā)現(xiàn)transferIndex=0,即所有node均已分配
發(fā)現(xiàn)擴(kuò)容線程已經(jīng)達(dá)到最大擴(kuò)容線程數(shù)