Java并發(fā)源碼分析ConcurrentHashMap線程集合
簡介
ConcurrentHashMap是一個線程安全的集合,底層是通過對指定索引位置上的節(jié)點進(jìn)行加鎖,而不是對整個數(shù)組加鎖,當(dāng)一個線程對指定索引位置上的節(jié)點加了鎖之后,其它線程就不能對該索引位置上的節(jié)點進(jìn)行操作,但可以對其它的索引位置上的節(jié)點操作,ConcurrentHashMap與HashMap有許多相似的地方,ConcurrentHashMap只是在一些產(chǎn)生線程安全的地方加了鎖,如果對HashMap了解的話,再來看ConcurrentHashMap就簡單許多。
常量
/** 數(shù)組最大容量大小(1073741824) */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** 默認(rèn)的初始容量大小 */ private static final int DEFAULT_CAPACITY = 16; /** 默認(rèn)的并發(fā)等級 */ private static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** 負(fù)載因子 */ private static final float LOAD_FACTOR = 0.75f; /** 鏈表轉(zhuǎn)換為紅黑樹的閾值 */ static final int TREEIFY_THRESHOLD = 8; /** 紅黑樹轉(zhuǎn)換為鏈表的閾值 */ static final int UNTREEIFY_THRESHOLD = 6; /** * 最小樹化閾值 * 當(dāng)鏈表中的節(jié)點數(shù)量大于等于8時,如果數(shù)組的長度小于最小樹化閾值則不會將鏈表轉(zhuǎn)換為紅黑樹,而是對數(shù)組進(jìn)行擴(kuò)容 * 將鏈表中的節(jié)點分散到別的索引節(jié)點中去,只有當(dāng)數(shù)組的長度大于等于最小樹化閾值則會將鏈表轉(zhuǎn)換為紅黑樹 */ static final int MIN_TREEIFY_CAPACITY = 64; /** 擴(kuò)容時,每個cpu最少需要負(fù)責(zé)的區(qū)域長度 */ private static final int MIN_TRANSFER_STRIDE = 16; /** 擴(kuò)容時生成標(biāo)記的二進(jìn)制所在位置 */ private static int RESIZE_STAMP_BITS = 16; /** 擴(kuò)容時記錄擴(kuò)容容量的標(biāo)記的二進(jìn)制所在位置 */ private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; //節(jié)點正在移動 static final int MOVED = -1; //節(jié)點已被樹化 static final int TREEBIN = -2; static final int RESERVED = -3; // hash for transient reservations //32位二進(jìn)制中正數(shù)最大的值,主要用于對key的hash值進(jìn)行二次運(yùn)算 static final int HASH_BITS = 0x7fffffff; /** 當(dāng)前機(jī)器的cpu數(shù)量 */ static final int NCPU = Runtime.getRuntime().availableProcessors(); /** * 存放元素的數(shù)組對象 * 只有在第一次添加元素的時候進(jìn)行初始化 * 如果沒有指定數(shù)組容量則使用默認(rèn)的容量16進(jìn)行初始化 * 數(shù)組容量為2的次方,如果指定的數(shù)組容量不是2的次方則會進(jìn)行計算獲取到大于指定容量并是2的次方的數(shù) */ transient volatile Node<K, V>[] table; /** 擴(kuò)容后的新數(shù)組,如果該數(shù)組不為空則說明當(dāng)前有其它線程正在進(jìn)行擴(kuò)容 */ private transient volatile Node<K, V>[] nextTable; /** 基本計數(shù)器值,在沒有線程爭用的時候才會使用 */ private transient volatile long baseCount; /** * -1時則說明數(shù)組對象正在初始化 * 如果數(shù)組未被初始化時該值為數(shù)組初始化時的大小 * 如果數(shù)組已被初始化該值為數(shù)組擴(kuò)容的閾值 * -N:低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M,M-1則是當(dāng)前正在擴(kuò)容的線程數(shù)量 */ private transient volatile int sizeCtl; /** * 擴(kuò)容時需要轉(zhuǎn)移舊數(shù)組中的剩余的索引位置長度 * 每個線程最少負(fù)責(zé)16個索引位置上的節(jié)點轉(zhuǎn)移 */ private transient volatile int transferIndex; /** * 計數(shù)器數(shù)組的鎖標(biāo)識 * 用與初始化計數(shù)器數(shù)組以及擴(kuò)容和創(chuàng)建計數(shù)器對象的時候使用 */ private transient volatile int cellsBusy; /** 計數(shù)器數(shù)組 */ private transient volatile CounterCell[] counterCells;
構(gòu)造方法
/**
* 創(chuàng)建一個默認(rèn)容量為16的數(shù)組對象
* 該構(gòu)造方法中并沒有執(zhí)行任何操作
* 并沒有創(chuàng)建默認(rèn)容量的數(shù)組對象
* 只有在第一次添加元素的時候才會初始化數(shù)組對象
*/
public ConcurrentHashMap() {
}
/**
* 創(chuàng)建一個指定初始容量的數(shù)組對象
* @param initialCapacity 指定的初始容量
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
//校驗指定的初始容量大小是否大于等于最大容量大小的一半
//如果大于等于最大容量大小的一半則在后續(xù)第一次添加元素的時候使用最大容量大小創(chuàng)建數(shù)組
//如果小于最大容量大小的一半則根據(jù)指定容量來計算最接近指定容量大小并大于指定容量的2的次方
//此處有一個疑問,指定容量大小如果等于最大容量大小的一半則說明指定容量大小是2的29次方,為什么不使用指定容量大小,而是使用最大的容量大小?
//從tableSizeFor方法也能看出,如果指定的初始容量大小本來就是2的次方的話并不會使用指定的初始容量大小來創(chuàng)建數(shù)組對象
//而是使用大于指定初始容量大小的2的次方來創(chuàng)建數(shù)組對象,比如指定的初始容量大小為16,那就會使用32來創(chuàng)建數(shù)組對象
//在HashMap中如果指定的初始容量大小本來就是2的次方的話則會使用指定的初始容量大小來創(chuàng)建數(shù)組對象
//并不會像ConcurrentHashMap一樣會取比當(dāng)前指定的2的次方的初始容量大小大的2的次方的容量大小來創(chuàng)建數(shù)組對象
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
//將計算后的容量大小賦值給sizeCtl等待初始化
this.sizeCtl = cap;
}
/**
* 根據(jù)指定的集合中的元素來創(chuàng)建新的數(shù)組對象
* @param m the map
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
//使用默認(rèn)的初始容量大小等待初始化
this.sizeCtl = DEFAULT_CAPACITY;
//初始化新的數(shù)組對象并將指定集合中的元素添加到新的數(shù)組對象中
putAll(m);
}
/**
* 根據(jù)指定的初始容量大小和指定的負(fù)載因子來創(chuàng)建新的數(shù)組對象
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* 根據(jù)指定的初始容量大小和指定的負(fù)載因子和并發(fā)數(shù)來創(chuàng)建新的數(shù)組對象
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel)
//初始容量大小小于并發(fā)數(shù)則使用并發(fā)數(shù)來創(chuàng)建數(shù)組對象
initialCapacity = concurrencyLevel;
//通過計算獲取到擴(kuò)容的閾值
long size = (long) (1.0 + (long) initialCapacity / loadFactor);
//如果閾值大于等于最大容量大小則使用最大容量大小來創(chuàng)建新的數(shù)組對象
//如果閾值小于最大容量大小則使用閾值來計算獲取到大于閾值并是2的次方的值
//使用計算后的值來創(chuàng)建新的數(shù)組對象
int cap = (size >= (long) MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int) size);
//將計算后的容量大小賦值給sizeCtl等待初始化
this.sizeCtl = cap;
}
在構(gòu)造方法中并沒有初始化數(shù)組對象,而是在第一次添加元素的時候才會進(jìn)行初始化,根據(jù)指定的初始容量大小和指定的負(fù)載因子進(jìn)行初始化,如果沒有指定初始容量或負(fù)載因子則使用默認(rèn)的,而數(shù)組的大小必須是2的次方,最大的數(shù)組大小則是2的30次方,如果大于2的30次方的話,數(shù)組則不會將繼續(xù)進(jìn)行擴(kuò)容,當(dāng)數(shù)組的容量大小已到達(dá)最大時,后續(xù)添加的元素則會掛載到鏈表或紅黑樹上,如果說指定的初始容量不是2的次方時則會調(diào)用tableSizeFor方法計算出大于指定初始容量并且是2的次方的值,如果說指定的初始容量本身就是2的次方時通過計算出的值還是本身。
此處就有一個疑問,本身的值不是2的次方的時候會通過計算獲取到大于指定的初始容量并且是最接近指定的初始容量的2的次方的值,如果本身的值就是2的次方的時候則會取本身的值,但是在第二個構(gòu)造方法中的tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1))方法是有問題的,假如initialCapacity 為16,按理來說最終數(shù)組初始化的容量大小應(yīng)該也為16,但是經(jīng)過該方法計算最終的數(shù)組初始化的容量大小為32,雖然該構(gòu)造方法有問題,但是并不影響。
put
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null)
//key和value不能為空
throw new NullPointerException();
//將key的hash值的高16位二進(jìn)制參加運(yùn)算獲取到新的hash值
int hash = spread(key.hashCode());
//指定索引位置上節(jié)點的數(shù)量
int binCount = 0;
for (Node<K, V>[] tab = table; ; ) {
//數(shù)組中指定索引的節(jié)點
Node<K, V> f;
//n 數(shù)組長度
//i key所在的數(shù)組索引位置
//fh 指定索引的節(jié)點的hash值
int n, i, fh;
//校驗數(shù)組是否已經(jīng)初始化
if (tab == null || (n = tab.length) == 0)
//數(shù)組未初始化則初始化數(shù)組
//并返回初始化完成的數(shù)組對象
tab = initTable();
//通過數(shù)組索引長度與key的hash值進(jìn)行與運(yùn)算獲取到指定索引位置上的元素節(jié)點,并校驗元素節(jié)點是否為空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//指定索引位置上的節(jié)點不存在則使用傳遞進(jìn)來的key和value創(chuàng)建新的節(jié)點
//并將新的節(jié)點放置到指定索引位置上
if (casTabAt(tab, i, null,new Node<K, V>(hash, key, value, null)))
//節(jié)點添加成功則退出循環(huán)
break;
//校驗索引位置上的節(jié)點是否正在移動
} else if ((fh = f.hash) == MOVED)
//如果索引位置上的節(jié)點正在移動則幫忙移動節(jié)點到新的數(shù)組中
//協(xié)助擴(kuò)容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//使用鎖鎖住指定索引位置上的節(jié)點
//這樣其它線程就不能對該索引位置上的節(jié)點進(jìn)行操作
//其它線程可以對其它索引位置上的節(jié)點進(jìn)行操作
synchronized (f) {
//校驗指定索引位置上的節(jié)點是否被更改
if (tabAt(tab, i) == f) {
//校驗節(jié)點的hash值是否大于等于0
//如果節(jié)點的hash值大于等于0則說明該索引位置上只有一個節(jié)點或節(jié)點是一個鏈表節(jié)點
if (fh >= 0) {
binCount = 1;
//從指定索引位置上的節(jié)點開始遍歷
for (Node<K, V> e = f; ; ++binCount) {
K ek;
//校驗遍歷到的節(jié)點的hash值以及key是否與傳遞的key和計算的hash值相同
//如果相同則根據(jù)onlyIfAbsent參數(shù)來決定是否需要使用新的value替換舊的value
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
//獲取舊的value
oldVal = e.val;
if (!onlyIfAbsent)
//使用新的value替換舊的value
e.val = value;
break;
}
//遍歷到的節(jié)點的hash值以及key不相同
//則判斷當(dāng)前遍歷到的節(jié)點是否是鏈表上的最后一個節(jié)點
//如果不是最后一個節(jié)點則繼續(xù)遍歷
//如果是最后一個節(jié)點則為key和value創(chuàng)建一個新的節(jié)點
//并將原尾節(jié)點的next指針指向新創(chuàng)建的節(jié)點
Node<K, V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K, V>(hash, key, value, null);
break;
}
}
//校驗節(jié)點是否是一個紅黑樹節(jié)點
} else if (f instanceof TreeBin) {
Node<K, V> p;
binCount = 2;
//將指定的key和value封裝成一個樹節(jié)點并添加到紅黑樹中
//如果紅黑樹中已經(jīng)存在相同key的節(jié)點則根據(jù)onlyIfAbsent參數(shù)來決定是否使用新的value替換紅黑樹中的節(jié)點的value
if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key,value)) != null) {
//獲取節(jié)點中的舊值
oldVal = p.val;
if (!onlyIfAbsent)
//替換節(jié)點中的value
p.val = value;
}
}
}
}
if (binCount != 0) {
//校驗索引位置上的節(jié)點的數(shù)量是否到達(dá)樹化的閾值
//如果該索引位置上的節(jié)點本來就是樹節(jié)點則不會繼續(xù)樹化
//只有當(dāng)索引位置上的節(jié)點是鏈表節(jié)點并且節(jié)點的數(shù)量大于等于8的時候才會樹化
if (binCount >= TREEIFY_THRESHOLD)
//索引位置上的節(jié)點數(shù)量已經(jīng)到達(dá)樹化的閾值
//則將該索引位置上的所有節(jié)點轉(zhuǎn)換為樹節(jié)點
treeifyBin(tab, i);
if (oldVal != null)
//返回被替換的舊值
return oldVal;
break;
}
}
}
//更新集合中元素節(jié)點的數(shù)量,并校驗是否需要擴(kuò)容
addCount(1L, binCount);
return null;
}
其實putval中的邏輯并不難,主要還是putval中調(diào)用的其它一些方法比較難以理解,我們就一步步的來看。
首先如果key和value為null的情況下是不能添加到當(dāng)前集合中的,再看spread方法,該方法是將key本身的hash值的高16位參加運(yùn)算獲取到新的hash值,如果數(shù)組的長度的二進(jìn)制是在低16位二進(jìn)制中就會導(dǎo)致key高16位的二進(jìn)制沒有參加運(yùn)算,容易導(dǎo)致運(yùn)算后的key的索引位置發(fā)生沖突,所以需要將高16位二進(jìn)制無符號右移16位與原hash值的二進(jìn)制進(jìn)行運(yùn)算減少索引沖突。
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
為什么要與HASH_BITS進(jìn)行與運(yùn)算呢? HASH_BITS是32位二進(jìn)制中最大的正整數(shù),與HASH_BITS進(jìn)行與運(yùn)算其實就是讓計算后的hash值保持在正整數(shù)。
后續(xù)整個代碼都被for循環(huán)包裹著,只有當(dāng)添加元素或替換元素成功時才會退出,而for循環(huán)里總共有4個大的分支。
1.數(shù)組未初始化,此時就需要調(diào)用initTable方法初始化數(shù)組對象,之前也說過數(shù)組對象是在第一次添加元素的時候初始化的,這是一種懶加載的思想,當(dāng)你使用到的時候才會去初始化。
initTable
private final Node<K, V>[] initTable() {
Node<K, V>[] tab;
int sc;
//校驗數(shù)組是否未被初始化
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
//數(shù)組長度小于0則說明其它線程正在準(zhǔn)備初始化數(shù)組
//當(dāng)前線程則需要讓出cpu讓其它線程初始化數(shù)組
Thread.yield();
//通過cas操作將sizeCtl修改成-1,告知其它線程當(dāng)前線程正在準(zhǔn)備初始化數(shù)組
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//如果指定了初始容量則使用指定的初始容量來創(chuàng)建數(shù)組
//如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
//初始化數(shù)組
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
//將初始化的數(shù)組對象賦值給當(dāng)前集合中的數(shù)組對象
//并賦值給tab,下一次循環(huán)校驗則會發(fā)現(xiàn)tab不為空則會退出循環(huán)并退出當(dāng)前方法
table = tab = nt;
//計算數(shù)組擴(kuò)容的閾值
sc = n - (n >>> 2);
}
} finally {
//如果try中的代碼塊執(zhí)行失敗時sizeCtl則是原先數(shù)組的長度
//執(zhí)行成功時sizeCtl則是擴(kuò)容的閾值
sizeCtl = sc;
}
break;
}
}
//數(shù)組已被初始化返回數(shù)組
return tab;
}
我們來看initTable方法中的代碼,首先whlie循環(huán)中校驗了數(shù)組是否還沒被初始化,當(dāng)數(shù)組未被初始化的時候,當(dāng)前線程則會去嘗試初始化數(shù)組,直到數(shù)組被當(dāng)前線程或者其它線程初始化成功。
先看(sc = sizeCtl) < 0這段代碼,sizeCtl小于0分為兩種情況,sizeCtl為-1的時候則說明當(dāng)前有其它線程正在初始化數(shù)組對象,sizeCtl小于-1的時候則說明當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,從當(dāng)前情況來看數(shù)組正在進(jìn)行擴(kuò)容的這種情況是不大可能存在的,所以說只有可能其它線程正在進(jìn)行初始化數(shù)組,當(dāng)其它線程正在初始化數(shù)組對象時,那當(dāng)前線程則需要讓出cpu的執(zhí)行權(quán),等待初始化數(shù)組對象的線程執(zhí)行完成。
再看后一個判斷,該判斷是一個cas操作,將sizeCtl修改成-1,告知其它線程當(dāng)前線程正在初始化數(shù)組,如果當(dāng)前線程修改失敗,則說明有其它線程搶先修改了sizeCtl的值,那當(dāng)前線程則需要重新走while循環(huán)來查看數(shù)組是否初始化完成,如果沒有完成那需要讓出cpu的執(zhí)行權(quán),如果數(shù)組已經(jīng)被其它線程初始化完成,那當(dāng)前線程則可以對數(shù)組進(jìn)行操作,如果說當(dāng)前線程執(zhí)行cas操作成功,則會先查看是否指定了數(shù)組初始的容量大小,如果指定了則使用指定的初始容量大小來創(chuàng)建數(shù)組對象,如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組對象,該指定的初始容量并非是真正的初始容量,如果說指定的初始容量是2的次方則會使用該容量大小來創(chuàng)建數(shù)組對象,如果說指定的初始容量不是2的次方的時候則會通過計算來獲取大于這個指定的初始容量并且是最接近該初始容量的2的次方,并使用計算后的值來初始化數(shù)組對象,當(dāng)初始化完成數(shù)組對象時則會計算下一次數(shù)組擴(kuò)容的閾值。
在initTable方法中我們看到代碼中使用一個變量sizeCtl來區(qū)分多種情況,其實sizeCtl一共分為4種情況,那我們先了解一下分為那4種。
sizeCtl = N && table = null:當(dāng)數(shù)組還未被初始化的時候,sizeCtl則是數(shù)組初始化的容量大小sizeCtl = N:當(dāng)數(shù)組已經(jīng)被初始化了的時候,sizeCtl則是數(shù)組下一次擴(kuò)容的閾值sizeCtl = -1:說明當(dāng)前數(shù)組對象正在被初始化sizeCtl = -N:當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,-N的低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M,M-1則是當(dāng)前正在進(jìn)行擴(kuò)容的線程數(shù)量,-N的高16位二進(jìn)制則是本次擴(kuò)容的標(biāo)記
2.通過spread方法計算后的hash值與數(shù)組索引長度進(jìn)行與運(yùn)算獲取到當(dāng)前元素節(jié)點所在的索引位置,并調(diào)用tabAt方法根據(jù)索引位置計算該索引中的元素節(jié)點在內(nèi)存中的偏移量,再根據(jù)偏移量從table數(shù)組中獲取元素節(jié)點,如果該元素節(jié)點為空則根據(jù)指定的key和value創(chuàng)建一個新的元素節(jié)點并調(diào)用casTabAt方法根據(jù)cas操作將新的元素節(jié)點添加到數(shù)組中指定的索引位置上。
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
//獲取數(shù)組中第一個元素在內(nèi)存中開始的偏移量
ABASE = U.arrayBaseOffset(ak);
//獲取數(shù)組中元素在內(nèi)存中的增量地址
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
//獲取增量地址在32位二進(jìn)制情況下高位連續(xù)包含0的個數(shù)
//使用31減去包含0的個數(shù)獲取到需要位移的次數(shù)
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
在看tabAt方法和casTabAt方法之前,我們先了解一下ConcurrentHashMap中靜態(tài)代碼塊中的一些代碼,通過getUnsafe()方法獲取到Unsafe類,Unsafe類是一個不安全的類,能通過該類直接對內(nèi)存中的一些資源進(jìn)行操作,U.objectFieldOffset方法能獲取到指定名稱變量在內(nèi)存中的偏移量,后續(xù)能根據(jù)該偏移量直接對變量進(jìn)行操作,U.arrayBaseOffset方法獲取指定類型數(shù)組中第一個元素在內(nèi)存中的偏移量,ABASE則是數(shù)組中第一個元素在內(nèi)存中開始的偏移量,U.arrayIndexScale方法獲取指定類型數(shù)組中元素在內(nèi)存中的增量地址,ASHIFT則是通過增量地址在32位二進(jìn)制的情況下高位連續(xù)為0的個數(shù),并使用31減去連續(xù)為0的個數(shù)獲取到需要位移的次數(shù)。
tabAt
static final <K, V> Node<K, V> tabAt(Node<K, V>[] tab, int i) {
//根據(jù)指定的索引位置計算該索引中的節(jié)點在內(nèi)存中的偏移量
//并從tab數(shù)組中根據(jù)索引所在的內(nèi)存的偏移量獲取節(jié)點
return (Node<K, V>) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}
i << ASHIFT獲取到第一個索引位置到索引i的增量地址,再加上ABASE即可獲取到索引i在內(nèi)存中的偏移量,然后在根據(jù)偏移量從volatile類型的tab數(shù)組中獲取節(jié)點。
casTabAt
static final <K, V> boolean casTabAt(Node<K, V>[] tab, int i,Node<K, V> c, Node<K, V> v) {
//根據(jù)指定的索引位置計算該索引中的節(jié)點在內(nèi)存中的偏移量
//并根據(jù)偏移量獲取節(jié)點,并將獲取到的節(jié)點與傳遞的節(jié)點c進(jìn)行比較
//如果兩個節(jié)點相同則使用節(jié)點v進(jìn)行替換
return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}
根據(jù)指定的索引位置的內(nèi)存偏移量獲取節(jié)點,如果獲取到的節(jié)點與傳遞的節(jié)點c相同則使用節(jié)點v進(jìn)行替換,該方法用于添加一個新的節(jié)點或替換節(jié)點,添加新的節(jié)點的時候,只需要將節(jié)點c設(shè)置為null,替換節(jié)點的時候,節(jié)點c則需要設(shè)置為原索引位置上的節(jié)點。
3.當(dāng)指定索引位置上的節(jié)點不為null的時候,但是節(jié)點的hash值為-1,此時說明有其它線程正在對數(shù)組進(jìn)行擴(kuò)容,并且指定索引位置上的節(jié)點已經(jīng)轉(zhuǎn)移到了新的數(shù)組中,此時當(dāng)前線程就需要調(diào)用helpTransfer方法協(xié)助其它線程進(jìn)行擴(kuò)容,當(dāng)協(xié)助完成時則會對新的數(shù)組進(jìn)行操作。
helpTransfer
final Node<K, V>[] helpTransfer(Node<K, V>[] tab, Node<K, V> f) {
Node<K, V>[] nextTab;
int sc;
//f instanceof ForwardingNode 當(dāng)前節(jié)點正在轉(zhuǎn)移
//nextTab = ((ForwardingNode<K, V>) f).nextTable) != null 擴(kuò)容的新數(shù)組是否不為空
//如果條件都為true則說明有線程正在擴(kuò)容中,當(dāng)前線程則需要協(xié)助擴(kuò)容
if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K, V>) f).nextTable) != null) {
//擴(kuò)容標(biāo)記
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {
//(sc >>> RESIZE_STAMP_SHIFT) != rs 校驗擴(kuò)容標(biāo)識是否一致,不一致則說明不是同一個數(shù)組
//sc == rs + 1 應(yīng)該是sc == (rs << 16) + 1 校驗擴(kuò)容是否完成
//sc == rs + MAX_RESIZERS 應(yīng)該是sc == (rs << 16) + MAX_RESIZERS 校驗擴(kuò)容的線程的數(shù)量是否到達(dá)最大
//transferIndex <= 0 舊數(shù)組中還有多少沒有轉(zhuǎn)移的索引節(jié)點長度,如果小于等于0則說明已經(jīng)轉(zhuǎn)移完成則不需要協(xié)助
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
//擴(kuò)容線程加1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
//協(xié)助擴(kuò)容
transfer(tab, nextTab);
break;
}
}
//擴(kuò)容完成則返回新的數(shù)組,對新的數(shù)組進(jìn)行操作
return nextTab;
}
//當(dāng)前節(jié)點還未被轉(zhuǎn)移或新的數(shù)組還沒有被初始化則對舊數(shù)組進(jìn)行操作
return table;
}
我們來看一下協(xié)助擴(kuò)容的方法,局部變量nextTab和sc則是最新的擴(kuò)容數(shù)組和擴(kuò)容標(biāo)識以及擴(kuò)容的線程數(shù)量。
tab != null和f instanceof FowardingNode:該條件只是為了代碼的健壯性,tab !=null從代碼來看,既然當(dāng)前線程執(zhí)行到了協(xié)助擴(kuò)容的方法,那tab肯定是不為null的,再看f instanceof FowardingNode條件,在之前的判斷中f的節(jié)點的hash值為-1,-1就已經(jīng)代表節(jié)點已經(jīng)被轉(zhuǎn)移,只有當(dāng)節(jié)點已經(jīng)轉(zhuǎn)移的時候才會執(zhí)行到當(dāng)前方法中,此時你就可以發(fā)現(xiàn)之前為什么要將節(jié)點的hash值控制在正整數(shù),如果說沒有控制在正整數(shù)的時候,當(dāng)節(jié)點的hash值恰巧為-1的時候,就會導(dǎo)致線程執(zhí)行一些沒有必要的代碼,而且在后續(xù)也不好區(qū)分鏈表節(jié)點。(nextTab = ((ForwardingNode<K, V>) f).nextTable) != null:該條件則是獲取最新的擴(kuò)容數(shù)組并校驗最新的數(shù)組是否不為null。
當(dāng)上述條件都為true的時候則會通過resizeStamp方法使用舊數(shù)組的長度生成一個擴(kuò)容標(biāo)識,再看while循環(huán)中的條件語句。
nextTab == nextTable:校驗當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組是否是最新的數(shù)組,如果此時nextTable == null的話則說明已經(jīng)完成了擴(kuò)容,那當(dāng)前線程則不需要協(xié)助擴(kuò)容了,只需要執(zhí)行自己該執(zhí)行的操作,如果說當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組不是最新的擴(kuò)容數(shù)組的時候,則會從nextTable中繼續(xù)獲取f節(jié)點,再從f節(jié)點中獲取nextTable,繼續(xù)校驗是否是最新的擴(kuò)容數(shù)組。
table == tab:校驗擴(kuò)容是否完成。
(sc = sizeCtl) < 0:校驗是否正在擴(kuò)容中。
上述條件都為true的時候則說明數(shù)組正在進(jìn)行擴(kuò)容中,我們先來看一些在哪些條件下當(dāng)前線程不會去協(xié)助擴(kuò)容。
(sc >>> RESIZE_STAMP_SHIFT) != rs:校驗擴(kuò)容標(biāo)識是否一致,不一致則說明不是對同一長度的數(shù)組進(jìn)行的擴(kuò)容。
sc == rs + 1:該條件應(yīng)該是sc == (rs << 16) + 1,校驗擴(kuò)容是否完成,在擴(kuò)容開始的時候sizeCtl為(rs << 16) + 2,這個2代表著開始擴(kuò)容的線程以及最后一個擴(kuò)容完成的線程,在transfer擴(kuò)容方法中,每一個線程完成了所否則的區(qū)域的時候都會將sizeCtl的值減1,當(dāng)最后一個線程執(zhí)行完成時減1,此時sizeCtl的值就等于(rs<< 16) + 1則說明整個數(shù)組擴(kuò)容都已經(jīng)完成了,已經(jīng)不需要線程協(xié)助了。
sc == rs + MAX_RESIZERS:該條件應(yīng)該是sc == (rs << 16) + MAX_RESIZERS,校驗當(dāng)前正在擴(kuò)容的線程的數(shù)量是否到達(dá)最大值(65535)。
transferIndex <= 0:校驗舊數(shù)組中還未完成轉(zhuǎn)移的索引節(jié)點長度是否小于等于0,小于等于0則說明已經(jīng)完成了轉(zhuǎn)移則不需要協(xié)助。
當(dāng)上述條件都不成立的時候則說明數(shù)組擴(kuò)容需要協(xié)助,此時通過cas操作將擴(kuò)容線程的數(shù)量加1并調(diào)用transfer方法來協(xié)助擴(kuò)容,該擴(kuò)容方法放在后面講解。
4.當(dāng)上面3個分支都不成立的時候,則說明該索引位置上的節(jié)點不為空并且沒有被轉(zhuǎn)移,此時就需要使用synchronized對指定索引位置上的節(jié)點加鎖,然后根據(jù)節(jié)點的類型來執(zhí)行相關(guān)的操作。
- 鏈表節(jié)點:鏈表節(jié)點的
hash值都是大于等于0的,當(dāng)指定索引位置上的節(jié)點是一個鏈表節(jié)點的時候,則會從鏈表的頭節(jié)點開始遍歷并且記錄鏈表中節(jié)點的數(shù)量,當(dāng)鏈表中的節(jié)點的key與指定的key相同則根據(jù)onlyIfAbsent參數(shù)來決定是否需要替換value,如果沒有相同的key時,則會為指定的key和value創(chuàng)建一個新的節(jié)點,并將新的節(jié)點添加到鏈表的尾部。 - 紅黑樹節(jié)點:紅黑樹的頭節(jié)點的
hash值固定為-2,此處直接校驗的是頭節(jié)點的類型是否是紅黑樹,當(dāng)指定索引位置上的頭節(jié)點是一個紅黑樹節(jié)點,則會調(diào)用頭節(jié)點的putTreeVal方法將指定的key和value添加到紅黑樹中,如果說指定的key已經(jīng)存在在紅黑樹中則會根據(jù)onlyIfAbsent參數(shù)來決定是否需要替換value,此處為什么是調(diào)用頭節(jié)點的putTreeVal方法呢?看到后面鏈表轉(zhuǎn)換為紅黑樹節(jié)點的時候就能明白了。
putTreeVal
final TreeNode<K, V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
for (TreeNode<K, V> p = root; ; ) {
int dir, ph;
K pk;
if (p == null) {
first = root = new TreeNode<K, V>(h, k, v, null, null);
break;
} else if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K, V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K, V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
TreeNode<K, V> x, f = first;
first = x = new TreeNode<K, V>(h, k, v, f, xp);
if (f != null)
//將新添加的樹節(jié)點設(shè)置為鏈表的頭節(jié)點
f.prev = x;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
if (!xp.red)
x.red = true;
else {
//獲取寫鎖,如果獲取寫鎖失敗則會將當(dāng)前線程掛起進(jìn)行等待
//如果有線程正在讀,那當(dāng)前寫的線程就要掛起進(jìn)行等待
//當(dāng)讀的線程執(zhí)行完成之后就會去喚醒掛起的線程
//如果說當(dāng)前線程在寫,有線程準(zhǔn)備讀
//那讀線程并不會去讀取紅黑樹中的節(jié)點
//而是讀取鏈表的節(jié)點,因為TreeBin對象中包含了紅黑樹的根節(jié)點并且也包含了鏈表的頭節(jié)點
//如果發(fā)生寫寫的問題呢?
//其實并不會發(fā)生寫寫的問題,因為當(dāng)前整個方法都被外部的synchronized鎖住了
lockRoot();
try {
//平衡紅黑樹中的節(jié)點
root = balanceInsertion(root, x);
} finally {
//釋放鎖
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
putTreeVal和balanceInsertion方法在之前的HashMap中已經(jīng)講過,此處就不再講解,我們主要還是看一下lockRoot和unlockRoot方法。
鎖狀態(tài)
//鎖狀態(tài) volatile int lockState; //寫鎖 static final int WRITER = 1; // set while holding write lock //等待 static final int WAITER = 2; // set when waiting for write lock //讀鎖 static final int READER = 4; // increment value for setting read lock
lockRoot
private final void lockRoot() {
//嘗試加寫鎖
if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
//加寫鎖失敗則嘗試將線程掛起進(jìn)行等待
contendedLock();
}
通過cas操作嘗試加寫鎖,當(dāng)加寫鎖失敗則會調(diào)用contendedLock方法將線程掛起進(jìn)行等待。
contendedLock
private final void contendedLock() {
boolean waiting = false;
for (int s; ; ) {
/**
* ~WAITER = 1111 1111 1111 1111 1111 1111 1111 1101
* ((s = lockState) & ~WAITER) == 0 等于0則說明當(dāng)前沒有線程對當(dāng)前TreeBin對象下的紅黑樹進(jìn)行加鎖
* 不等于則說明有線程對TreeBin對象下的紅黑樹加鎖
*/
if (((s = lockState) & ~WAITER) == 0) {
//沒有線程對TreeBin對象下的紅黑樹加鎖
//當(dāng)前線程則可以對TreeBin對象下的紅黑樹嘗試加鎖
if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
if (waiting)
//將等待線程清空
waiter = null;
return;
}
//(s & WAITER) == 0 執(zhí)行當(dāng)前判斷語句的時候則說明有線程對紅黑樹加了鎖
//如果(s & WAITER)等于0則說明沒有線程在等待加鎖
//此時當(dāng)前線程就可以進(jìn)行等待
} else if ((s & WAITER) == 0) {
//將鎖狀態(tài)的32位二進(jìn)制中的第2位設(shè)置為1
//代表當(dāng)前有一個線程正在等待加鎖
if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
waiting = true;
//將TreeBin中的等待線程設(shè)置為當(dāng)前線程
waiter = Thread.currentThread();
}
} else if (waiting)
//當(dāng)前線程已經(jīng)設(shè)置成了等待狀態(tài)了,此時就需要將線程掛起
LockSupport.park(this);
}
}
contendedLock方法中總共有3個分支,我們就一個一個的開始講解。
((s = lockState) & ~WAITER) == 0:校驗是否有線程對指定索引位置上的TreeBin對象中的紅黑樹進(jìn)行加鎖。
~WAITER = -3,32位二進(jìn)制為 1111 1111 1111 1111 1111 1111 1111 1101
WRITER = 1,1為寫鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0001
READER = 4,4為讀鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0100
使用最新的鎖狀態(tài)與~WAITER進(jìn)行與運(yùn)算,當(dāng)加了讀鎖或?qū)戞i時,讀鎖和寫鎖的二進(jìn)制標(biāo)識與~WAITER二進(jìn)制標(biāo)識都為1的情況下則說明已經(jīng)有線程加了鎖,如果有線程加了鎖,那當(dāng)前線程則會執(zhí)行后續(xù)的代碼來將自己掛起并等待。
(s & WAITER) == 0:執(zhí)行到當(dāng)前語句的時候就說明已經(jīng)有線程加了鎖,此時就需要校驗一下是否有線程掛起并在等待,從代碼上來看,并不會出現(xiàn)多個線程同時等待,只會存在一個等待的線程,如果沒有線程在等待,則會通過cas操作將鎖狀態(tài)加上一個有線程在等待的標(biāo)識,并將TreeBin對象中的等待線程設(shè)置為當(dāng)前線程。
waiting:當(dāng)有線程加了鎖,并且將當(dāng)前線程設(shè)置為了等待的線程,此時就需要將當(dāng)前線程掛起。
為什么說只會存在一個等待的線程呢?
讀鎖與寫鎖本就互斥,在get方法中,獲取紅黑樹中的節(jié)點的時候則會加上一個讀鎖,此時寫鎖就需要掛起進(jìn)行等待,當(dāng)讀鎖釋放完成之后就會喚醒加寫鎖的線程,如果已經(jīng)有線程加了寫鎖,那get方法則不會從紅黑樹中獲取節(jié)點,而是從TreeBin對象中記錄的鏈表的頭節(jié)點開始遍歷進(jìn)行匹配,那會不會發(fā)生多個線程同時去加寫鎖呢?其實并不會,因為加寫鎖的方法的外部整個都被synchronized鎖住了,所以并不會存在多個線程同時加寫鎖,也不會存在多個等待的線程。
鏈表添加節(jié)點和紅黑樹添加節(jié)點都已經(jīng)講解完畢,此時就要看一下后續(xù)代碼,鏈表節(jié)點在什么情況下會轉(zhuǎn)換為紅黑樹以及是怎么轉(zhuǎn)換的。
if (binCount != 0) {
//校驗索引位置上的節(jié)點的數(shù)量是否到達(dá)樹化的閾值
//如果該索引位置上的節(jié)點本來就是樹節(jié)點則不會繼續(xù)樹化
//只有當(dāng)索引位置上的節(jié)點是鏈表節(jié)點并且節(jié)點的數(shù)量大于等于8的時候才會樹化
if (binCount >= TREEIFY_THRESHOLD)
//索引位置上的節(jié)點數(shù)量已經(jīng)到達(dá)樹化的閾值
//則將該索引位置上的所有節(jié)點轉(zhuǎn)換為樹節(jié)點
treeifyBin(tab, i);
if (oldVal != null)
//返回被替換的舊值
return oldVal;
break;
}
在添加一個節(jié)點到鏈表中去,會從鏈表的頭節(jié)點開始遍歷并記錄鏈表中的節(jié)點數(shù)量,該數(shù)量就是用binCount記錄的,當(dāng)鏈表中的節(jié)點數(shù)量大于等于TREEIFY_THRESHOLD(8)的時候則會調(diào)用treeifyBin方法來決定是否需要將鏈表中的所有節(jié)點樹化并轉(zhuǎn)換為紅黑樹。
treeifyBin
private final void treeifyBin(Node<K, V>[] tab, int index) {
//指定索引位置上的節(jié)點
Node<K, V> b;
//n 數(shù)組長度
int n, sc;
if (tab != null) {
//校驗數(shù)組的長度是否小于最小的樹化閾值
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
/**
* 數(shù)組長度小于最小的樹化閾值
* 此時并不會將數(shù)組中的鏈表節(jié)點轉(zhuǎn)換為樹節(jié)點
* 只會將數(shù)組進(jìn)行擴(kuò)容
*
* 如果仔細(xì)查看tryPresize方法你會發(fā)現(xiàn)該方法中也對n進(jìn)行了左移1位
* 當(dāng)前tryPresize傳參的時候就已經(jīng)對n進(jìn)行了左移1位,然后在方法里面又對n進(jìn)行了左移1位
* 這樣是不是就對數(shù)組進(jìn)行了4倍擴(kuò)容,并不是我們想的那樣2倍擴(kuò)容呢?
* 其實不是的,在tryPresize方法里面并沒有使用2次位移后的n來擴(kuò)容
* 而是使用最開始的數(shù)組長度n來計算進(jìn)行擴(kuò)容
*/
tryPresize(n << 1);
//獲取指定索引位置上的元素節(jié)點并校驗元素節(jié)點是否不為空
//如果元素節(jié)點不為空則校驗該節(jié)點的hash值是大于等于0
//如果hash值大于等于0則說明該節(jié)點需要轉(zhuǎn)換為樹節(jié)點
//如果hash值小于0則說明該節(jié)點正在移動或已經(jīng)被樹化了
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
//使用鎖鎖住指定索引位置上的節(jié)點
//在添加元素的時候也會對指定索引位置上的節(jié)點加鎖
//如果兩個索引位置都是相同的索引位置的時候,如果添加元素的線程先加了鎖
//那執(zhí)行到當(dāng)前方法的線程則會等待添加元素的線程釋放鎖
//當(dāng)添加元素的線程釋放了鎖之后,當(dāng)前方法的線程則會獲取鎖將索引位置上所有的節(jié)點都樹化,包括新添加的元素節(jié)點
//如果是執(zhí)行到當(dāng)前方法的線程獲取到了鎖,那添加元素的線程則會等待
//當(dāng)指定索引位置上的所有節(jié)點都樹化了之后并釋放了鎖
//添加元素的線程則會去獲取鎖,此時添加元素的線程會發(fā)現(xiàn)當(dāng)前索引位置上的節(jié)點已經(jīng)是樹節(jié)點
//則會將元素節(jié)點添加到紅黑樹中并使紅黑樹平衡
synchronized (b) {
//校驗在獲取鎖的時候指定索引位置上的節(jié)點是否被更改
if (tabAt(tab, index) == b) {
//hd 頭節(jié)點
//tl 當(dāng)前遍歷到的節(jié)點的上一個節(jié)點
TreeNode<K, V> hd = null, tl = null;
//從指定索引位置上的節(jié)點開始遍歷,依次將鏈表上的所有節(jié)點轉(zhuǎn)換為樹節(jié)點
for (Node<K, V> e = b; e != null; e = e.next) {
//將普通節(jié)點轉(zhuǎn)換為樹節(jié)點
TreeNode<K, V> p = new TreeNode<K, V>(e.hash, e.key, e.val, null, null);
//校驗當(dāng)前遍歷的節(jié)點是否是鏈表中的頭節(jié)點
//如果是頭節(jié)點則將該節(jié)點設(shè)置為了樹節(jié)點的頭節(jié)點
//如果不是頭節(jié)點則將當(dāng)前轉(zhuǎn)換的樹節(jié)點與上一個樹節(jié)點進(jìn)行關(guān)聯(lián)
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
//創(chuàng)建一個TreeBin對象并將樹化的節(jié)點轉(zhuǎn)換為紅黑樹
//TreeBin對象中保留著紅黑樹的根節(jié)點以及鏈表的頭節(jié)點
//再調(diào)用setTabAt方法將TreeBin對象添加到指定的索引位置上
//在HashMap中并沒有創(chuàng)建一個TreeBin對象來存放紅黑樹的根節(jié)點
//而是直接將紅黑樹的根節(jié)點放置到指定的索引位置上
setTabAt(tab, index, new TreeBin<K, V>(hd));
}
}
}
}
}
(n = tab.length) < MIN_TREEIFY_CAPACITY:校驗數(shù)組的長度是否小于最小樹化的閾值,并不是說鏈表中的節(jié)點數(shù)量到達(dá)8了就要將鏈表節(jié)點轉(zhuǎn)換為紅黑樹節(jié)點,前提是需要數(shù)組的長度到達(dá)了閾值(64)才會轉(zhuǎn)換為紅黑樹節(jié)點,如果數(shù)組的長度沒有到達(dá)閾值則會進(jìn)行擴(kuò)容。
(b = tabAt(tab, index)) != null && b.hash >= 0:校驗外部synchronized釋放鎖之后,準(zhǔn)備節(jié)點樹化的期間是否有其它線程對該節(jié)點進(jìn)行操作了,該操作分為兩種,有線程將該索引位置上的所有節(jié)點都刪除了或者說有線程將該索引位置上的節(jié)點都樹化了,這兩種情況下就不需要再進(jìn)行樹化了。 我們主要看一下是怎么樹化的以及轉(zhuǎn)換成紅黑樹的,樹化的操作其實很簡單,就是對指定索引位置上的節(jié)點加鎖防止其它線程操作,依次從鏈表的頭節(jié)點開始遍歷,將鏈表中的Node節(jié)點轉(zhuǎn)換為TreeNode節(jié)點,就這樣樹化就完成了,而轉(zhuǎn)換為紅黑樹就是通過創(chuàng)建一個TreeBin對象來完成的,在構(gòu)造TreeBin對象時會將樹化的節(jié)點轉(zhuǎn)換為紅黑樹中的節(jié)點。
TreeBin
TreeBin(TreeNode<K, V> b) {
//給當(dāng)前TreeBin對象設(shè)置一個樹化標(biāo)識
super(TREEBIN, null, null, null);
//樹化的頭節(jié)點的設(shè)置為鏈表的頭節(jié)點
this.first = b;
//根節(jié)點
TreeNode<K, V> r = null;
//從樹化的頭節(jié)點開始遍歷將節(jié)點轉(zhuǎn)換為紅黑樹中的節(jié)點,直到?jīng)]有下一個節(jié)點
for (TreeNode<K, V> x = b, next; x != null; x = next) {
//下一個節(jié)點
next = (TreeNode<K, V>) x.next;
//初始化每個樹節(jié)點的左右子節(jié)點
x.left = x.right = null;
if (r == null) {
//根節(jié)點為空則將當(dāng)前遍歷到的節(jié)點設(shè)置為根節(jié)點
//并將該節(jié)點的顏色設(shè)置為黑色
x.parent = null;
x.red = false;
r = x;
} else {
//待添加到紅黑樹中的節(jié)點的key
K k = x.key;
//待添加到紅黑樹中的節(jié)點的hash值
int h = x.hash;
Class<?> kc = null;
//從紅黑樹的根節(jié)點開始遍歷來校驗節(jié)點放置的位置
for (TreeNode<K, V> p = r; ; ) {
//dir 節(jié)點添加到紅黑樹中的位置
//ph 被遍歷到的紅黑樹中的節(jié)點的key
int dir, ph;
//被遍歷到的紅黑樹中的節(jié)點的key
K pk = p.key;
if ((ph = p.hash) > h)
//被遍歷到的紅黑樹中的節(jié)點的hash值大于待添加到紅黑樹中的節(jié)點的hash值
//則需要將節(jié)點添加到紅黑樹中的左子節(jié)點
dir = -1;
else if (ph < h)
//被遍歷到的紅黑樹中的節(jié)點的hash值小于待添加到紅黑樹中的節(jié)點的hash值
//則需要將節(jié)點添加到紅黑樹中的右子節(jié)點
dir = 1;
else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K, V> xp = p;
//校驗待添加到紅黑樹中的節(jié)點所放置的位置是否有節(jié)點存在
//如果有節(jié)點存在則重新走循環(huán)與子節(jié)點繼續(xù)比較直到?jīng)]有了子節(jié)點
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//放置待添加到紅黑樹中的節(jié)點的位置沒有了節(jié)點
//則會將節(jié)點添加到紅黑樹中
//將待添加節(jié)點的父節(jié)點的指針指向當(dāng)前遍歷到的紅黑樹中的節(jié)點
x.parent = xp;
if (dir <= 0)
//將待添加的節(jié)點放置到被遍歷到的節(jié)點的左子節(jié)點
xp.left = x;
else
//將待添加的節(jié)點放置到被遍歷到的節(jié)點的右子節(jié)點
xp.right = x;
//紅黑樹中添加了新的節(jié)點,可能讓紅黑樹不在平衡
//所以需要將紅黑樹中的節(jié)點進(jìn)行平衡
r = balanceInsertion(r, x);
break;
}
}
}
}
//TreeBin對象記錄根節(jié)點
this.root = r;
assert checkInvariants(root);
}
TreeBin的構(gòu)造方法比較簡單,通過super方法給當(dāng)前的TreeBin對象設(shè)置hash值為-2,然后通過遍歷樹化后的鏈表節(jié)點,將鏈表的頭節(jié)點設(shè)置為紅黑樹的根節(jié)點,后續(xù)鏈表中的節(jié)點從紅黑樹的根節(jié)點開始進(jìn)行比較,來獲取到鏈表的節(jié)點在紅黑樹中所存放的位置,紅黑樹按左小右大的方式存放節(jié)點,每添加一個節(jié)點到紅黑樹中,都有可能讓紅黑樹不平衡,所以每次都會嘗試是否需要平衡紅黑樹中的節(jié)點。

上圖是鏈表轉(zhuǎn)紅黑樹,轉(zhuǎn)為紅黑樹之后,索引位置上是一個TreeBin對象,并且TreeBin對象中包含了紅黑樹的根節(jié)點以及鏈表的頭節(jié)點,而紅黑樹中的節(jié)點中不僅有紅黑樹本身的指針,而且還有鏈表的一些指針,既能當(dāng)做是紅黑樹也可以當(dāng)做鏈表,上圖中并沒有將所有節(jié)點的鏈表指針都畫出來,只是畫了部分節(jié)點的鏈表指針。
當(dāng)元素添加操作以及鏈表轉(zhuǎn)換為紅黑樹操作完成之后,我們再來看ConcurrentHashMap是如何記錄元素數(shù)量的以及擴(kuò)容的。
addCount
/**
* 添加元素節(jié)點或刪除元素節(jié)點的時候需要將計數(shù)器中的值修改
* 如果在單線程的情況下直接對基本計數(shù)器值修改
* 如果在多線程的情況下對基本計數(shù)器修改失敗的話并且計數(shù)器數(shù)組為空
* 則需要初始化計數(shù)器數(shù)組,并使用線程生成隨機(jī)數(shù)與計數(shù)器數(shù)組的長度進(jìn)行運(yùn)算獲取到指定的索引位置
* 并將指定索引位置上的計數(shù)器對象進(jìn)行初始化并將check值保存在計數(shù)器對象中的value
* 如果計數(shù)器數(shù)組不為空,但是指定的索引位置上的計數(shù)器對象為空
* 則初始化計數(shù)器對象并將check值保存在計數(shù)器對象中的value
* 如果計數(shù)器對象也不為空則直接將check值累加到計數(shù)器對象中的value
* 在將check值保存之后則會校驗當(dāng)前集合中的數(shù)組是否需要擴(kuò)容
* 如果需要擴(kuò)容則會調(diào)用transfer方法來進(jìn)行擴(kuò)容
*/
private final void addCount(long x, int check) {
//計數(shù)器數(shù)組
CounterCell[] as;
//b 基本計數(shù)器值
long b, s;
//計數(shù)器數(shù)組counterCells不為空則說明之前已經(jīng)發(fā)生過多線程對基本計數(shù)器baseCount進(jìn)行操作
//計數(shù)器數(shù)組counterCells為空則說明之前一直在單線程的情況下對基本計數(shù)器baseCount進(jìn)行操作
//如果計數(shù)器數(shù)組為空則會嘗試對基本計數(shù)器baseCount進(jìn)行操作,如果對baseCount操作失敗則說明當(dāng)前處于多線程的情況下
//此時就需要初始化counterCells
if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a;
long v;
int m;
boolean uncontended = true;
//as == null || (m = as.length - 1) < 0 校驗計數(shù)器數(shù)組是否初始化,如果沒有初始化則調(diào)用fullAddCount方法進(jìn)行初始化
//(a = as[ThreadLocalRandom.getProbe() & m]) == null 當(dāng)前計數(shù)器數(shù)組不為空的時候則使用線程生成的隨機(jī)數(shù)
//與計數(shù)器數(shù)組的長度進(jìn)行與運(yùn)算獲取到指定索引位置上的計數(shù)器對象,并校驗計數(shù)器對象是否為空
//如果計數(shù)器對象為空則調(diào)用fullAddCount方法對指定索引位置上的計數(shù)器對象進(jìn)行初始化
//!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) 計數(shù)器數(shù)組不為空并且運(yùn)算獲取到的指定索引位置上的計數(shù)器對象也不為空
//此時就可以對計數(shù)器對象進(jìn)行操作,如果操作失敗則說明有其它線程獲取到了這個索引位置上的計數(shù)器對象并對這個計數(shù)器進(jìn)行操作中
//此時就需要調(diào)用fullAddCount方法選擇其它索引位置上的計數(shù)器對象進(jìn)行操作
if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//對計數(shù)器數(shù)組以及計數(shù)器數(shù)組中的計數(shù)器對象進(jìn)行初始化
//一個線程對計數(shù)器對象操作失敗則會更換其它索引上的計數(shù)器對象進(jìn)行操作
//如果這個線程多次對計數(shù)器對象操作失敗并且當(dāng)前計數(shù)器數(shù)組的長度小于cpu的數(shù)量
//此時就會嘗試進(jìn)行擴(kuò)容,然后對擴(kuò)容之后的數(shù)組中的計數(shù)器對象進(jìn)行操作
//如果說計數(shù)器數(shù)組的長度不小于cpu的數(shù)量則不會進(jìn)行擴(kuò)容
//只會一直更換計數(shù)器對象進(jìn)行操作,直到操作成功
fullAddCount(x, uncontended);
return;
}
//校驗check是否小于等于1
//check=-1的情況下則說明刪除了指定索引位置上的元素節(jié)點
//check=0的情況下則說明指定索引位置上沒有元素節(jié)點,直接將指定的key和value封裝成一個節(jié)點放置到指定的索引位置上
//check=1的情況在put方法下則說明指定索引位置上有元素節(jié)點,但是只是對索引位置上的元素節(jié)點中的value進(jìn)行了替換
//在其它方法中check=1可能表示在指定的索引位置上添加了1個元素節(jié)點
//如果是這三種情況的話則不會去嘗試進(jìn)行擴(kuò)容
//如果執(zhí)行到了當(dāng)前判斷語句的話則說明并沒有執(zhí)行上面的fullAddCount方法
//而是通過上面判斷語句中的cas操作對指定索引位置上的計數(shù)器對象的值操作成功
//但是在check=1的情況下的put方法只是將原本的元素節(jié)點中的value替換了
//如果此時還要對計數(shù)器對象中的value加1,那不就會造成1個元素節(jié)點變成了2個元素節(jié)點?
//其實并不會的,如果check=1的情況下put方法并不會走到當(dāng)前的方法中
if (check <= 1)
return;
//將計數(shù)器數(shù)組中所有的計數(shù)器對象值以及基本的計數(shù)器值合并獲取到當(dāng)前集合中的元素節(jié)點的數(shù)量
s = sumCount();
}
//校驗check是否大于等于0
//大于等于0則說明添加了新的元素節(jié)點
//此時就需要來校驗是否需要進(jìn)行擴(kuò)容
//check一共分為5種情況,有三種已經(jīng)在上面說過,現(xiàn)在來看一下剩下的兩種情況
//check=2的情況下代表在紅黑樹種添加了一個樹節(jié)點,也有可能是在一個鏈表節(jié)點中添加了一個節(jié)點
//check>2的情況下代表在鏈表節(jié)點中添加了一個節(jié)點
if (check >= 0) {
Node<K, V>[] tab, nt;
int n, sc;
//當(dāng)前數(shù)組中的元素節(jié)點的數(shù)量已經(jīng)到達(dá)擴(kuò)容的閾值并且數(shù)組的長度小于數(shù)組最大容量大小
//此時會嘗試將數(shù)組進(jìn)行擴(kuò)容,如果擴(kuò)容失敗則會一直進(jìn)行嘗試,直到擴(kuò)容成功
while (s >= (long) (sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) {
//獲取擴(kuò)容時的標(biāo)記
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0)
break;
//擴(kuò)容線程加1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
//協(xié)助擴(kuò)容
transfer(tab, nt);
//將sizeCtl修改成一個很大的負(fù)數(shù),告知其它線程現(xiàn)在有線程正在擴(kuò)容
//為什么要+2而不是+1?+1代表當(dāng)前擴(kuò)容的線程數(shù)量+1,而+2可能是標(biāo)識開啟擴(kuò)容
} else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2))
//擴(kuò)容
transfer(tab, null);
//重新計算集合中元素節(jié)點的數(shù)量
s = sumCount();
}
}
}
addCount中分為兩個分支,分別為元素數(shù)量計數(shù)以及數(shù)組擴(kuò)容,我們先看計數(shù)的分支。
在單線程的情況下只會對基本計數(shù)器baseCount進(jìn)行操作,如果在多線程的情況下同時對baseCount進(jìn)行操作,只會有一個線程操作成功,而其它線程并不會等待繼續(xù)對baseCount進(jìn)行操作,而是調(diào)用fullAddCount方法初始化計數(shù)器數(shù)組(CounterCells)以及數(shù)組中的計數(shù)器對象(CounterCell)并對數(shù)組中的計數(shù)器對象進(jìn)行操作,只要出現(xiàn)過一次多線程的情況,往后都不會對baseCount操作了,而是直接對計數(shù)器數(shù)組中的計數(shù)器對象進(jìn)行操作,fullAddCount中代碼具體的意思可以參考下面代碼中的注釋,這里不再多講。
fullAddCount
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
//使用當(dāng)前線程生成隨機(jī)數(shù),如果隨機(jī)數(shù)為0則說明ThreadLocalRandom中的一些參數(shù)還未被初始化
//此時就需要進(jìn)行初始化并重新獲取隨機(jī)數(shù)
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false;
for (; ; ) {
//計數(shù)器數(shù)組
CounterCell[] as;
//計數(shù)器對象
CounterCell a;
//計數(shù)器數(shù)組長度
int n;
//計數(shù)器對象中的value
long v;
//校驗計數(shù)器數(shù)組是否已初始化
if ((as = counterCells) != null && (n = as.length) > 0) {
//校驗當(dāng)前線程生成的隨機(jī)數(shù)與計數(shù)器數(shù)組索引長度運(yùn)算后獲取到的指定索引位置上的計數(shù)器對象是否為空
if ((a = as[(n - 1) & h]) == null) {
//校驗計數(shù)器數(shù)組的鎖狀態(tài)是否為0
//如果不為0則說明有其它線程正在對計數(shù)器數(shù)組進(jìn)行擴(kuò)容或?qū)τ嫈?shù)器數(shù)組中的計數(shù)器對象進(jìn)行初始化
if (cellsBusy == 0) {
//創(chuàng)建一個計數(shù)器對象,并將值添加到計數(shù)器對象中
CounterCell r = new CounterCell(x);
//加鎖,防止多個線程同時對通一個索引位置上的計數(shù)器對象進(jìn)行初始化
if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try {
CounterCell[] rs;
int m, j;
//校驗一下運(yùn)算后的索引位置上的計數(shù)器對象是否為空
//如果不為空則說明已經(jīng)被其它線程初始化了,當(dāng)前線程就不需要再去初始化
if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
//將創(chuàng)建好的計數(shù)器對象添加到運(yùn)算后的指定索引位置上
rs[j] = r;
//初始化成功
created = true;
}
} finally {
//修改計數(shù)器數(shù)組鎖標(biāo)識
cellsBusy = 0;
}
if (created)
//初始化成功則退出
break;
//初始化失敗,說明已經(jīng)有其它線程初始化了計數(shù)器對象,此時就需要重新走循環(huán)選擇初始化成功的計數(shù)器對象進(jìn)行操作
continue;
}
}
//其它線程正在對計數(shù)器數(shù)組進(jìn)行擴(kuò)容或初始化指定索引位置上的計數(shù)器對象
collide = false;
} else if (!wasUncontended)
//wasUncontended為false則說明有其它線程正在對指定索引位置上的計數(shù)器對象進(jìn)行操作
//此時當(dāng)前線程則需要獲取其它索引位置上的計數(shù)器對象進(jìn)行操作
wasUncontended = true;
//嘗試對指定索引位置上的計數(shù)器對象進(jìn)行操作
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
//執(zhí)行成功則退出
break;
//校驗當(dāng)前是否有線程對計數(shù)器數(shù)組進(jìn)行了擴(kuò)容,將計數(shù)器數(shù)組中的計數(shù)器對象移動到了新的計數(shù)器數(shù)組中
//如果已經(jīng)將計數(shù)器對象移動到了新的計數(shù)器數(shù)組中,那重新選擇新的計數(shù)器數(shù)組中的計數(shù)器對象來進(jìn)行操作
//如果沒有線程對計數(shù)器數(shù)組進(jìn)行擴(kuò)容,那比較一下當(dāng)前計數(shù)器數(shù)組的長度是否大于等于cpu的數(shù)量
//如果大于等于cpu的數(shù)量則不會進(jìn)行擴(kuò)容,只會重新選擇計數(shù)器數(shù)組中的計數(shù)器對象來操作
//如果計數(shù)器數(shù)組的長度小于cpu的數(shù)量那就會對計數(shù)器數(shù)組進(jìn)行2倍的擴(kuò)容
else if (counterCells != as || n >= NCPU)
//此處將collide修改為false可能是想再次嘗試一下是否能對計數(shù)器數(shù)組中的計數(shù)器對象進(jìn)行操作
//如果能操作則不進(jìn)行擴(kuò)容,這樣能減少擴(kuò)容帶來的性能問題
collide = false;
else if (!collide)
collide = true;
//多次獲取計數(shù)器數(shù)組中的計數(shù)器對象而不能操作,并計數(shù)器數(shù)組的長度小于cpu的數(shù)量
//導(dǎo)致部分的cpu沒有被使用到,此時就需要對計數(shù)器數(shù)組進(jìn)行擴(kuò)容,充分的使用cpu
//對計數(shù)器數(shù)組加鎖
else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
//校驗計數(shù)器數(shù)組是否變更,防止其它線程已經(jīng)對計數(shù)器數(shù)組進(jìn)行了擴(kuò)容
if (counterCells == as) {
//初始化一個新的計數(shù)器數(shù)組,大小是原來的2倍
CounterCell[] rs = new CounterCell[n << 1];
//循環(huán)將原來的計數(shù)器數(shù)組中的計數(shù)器對象移動到擴(kuò)容后的計數(shù)器數(shù)組中
for (int i = 0; i < n; ++i)
rs[i] = as[i];
//更新當(dāng)前集合中的計數(shù)器數(shù)組
counterCells = rs;
}
} finally {
//修改計數(shù)器數(shù)組鎖標(biāo)識
cellsBusy = 0;
}
collide = false;
//使用擴(kuò)容后的計數(shù)器數(shù)組進(jìn)行操作
continue;
}
//計數(shù)器對象正在被其它線程操作,此時需要重新生成隨機(jī)數(shù),獲取別的索引位置上的計數(shù)器對象來進(jìn)行操作
h = ThreadLocalRandom.advanceProbe(h);
//計數(shù)器數(shù)組未被初始化,此時就需要校驗一下當(dāng)前是否有其它線程正在初始化計數(shù)器數(shù)組
//如果沒有,那當(dāng)前線程就需要將計數(shù)器數(shù)組初始化的標(biāo)識設(shè)置為1,代表當(dāng)前有線程正在初始化計數(shù)器數(shù)組
} else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try {
if (counterCells == as) {
//創(chuàng)建計數(shù)器數(shù)組,計數(shù)器數(shù)組的長度為2的次方
CounterCell[] rs = new CounterCell[2];
//創(chuàng)建一個計數(shù)器對象,并將值添加到計數(shù)器對象中
//并使用線程生成的隨機(jī)數(shù)與計數(shù)器數(shù)組索引長度進(jìn)行運(yùn)算獲取到指定的索引位置
//并將計數(shù)器對象放置到指定的索引位置上
rs[h & 1] = new CounterCell(x);
//更新當(dāng)前集合中的計數(shù)器數(shù)組
counterCells = rs;
//初始化完成
init = true;
}
} finally {
//修改計數(shù)器數(shù)組鎖標(biāo)識
cellsBusy = 0;
}
if (init)
//計數(shù)器數(shù)組初始化完成則退出
break;
//計數(shù)器數(shù)組正在被其它線程初始化或已經(jīng)被其它線程初始化完成
//此時嘗試對基本的計數(shù)器值進(jìn)行操作
//如果失敗則說明有其它線程也在對基本計數(shù)器值進(jìn)行操作
//那就要重新走循環(huán),來看計數(shù)器數(shù)組是否被初始化完成,如果被初始化完成那就對計數(shù)器數(shù)組中的計數(shù)器對象進(jìn)行操作
//如果還沒有初始化完成則會繼續(xù)嘗試對基本的計數(shù)器值進(jìn)行操作
} else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break;
}
}
我們再看addCount中的第二個分支,該分支中主要是先校驗是否到達(dá)了擴(kuò)容的閾值以及是否小于數(shù)組最大的容量大小,條件成立則會校驗是否有線程在擴(kuò)容,如果有線程在擴(kuò)容,那當(dāng)前線程則需要協(xié)助擴(kuò)容如果沒有那當(dāng)前線程則開啟擴(kuò)容。
transfer
private final void transfer(Node<K, V>[] tab, Node<K, V>[] nextTab) {
//n 數(shù)組長度
//stride 每個cpu需要負(fù)責(zé)轉(zhuǎn)移的索引長度
int n = tab.length, stride;
//使用數(shù)組長度來計算每個cpu需要負(fù)責(zé)的長度
//每個cpu最少需要負(fù)責(zé)16的長度
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
//nextTab為空則說明當(dāng)前沒有其它線程在初始化
//不為空則說明當(dāng)前有其它線程正在初始化,當(dāng)前線程則需要幫助初始化的線程將舊數(shù)組中的元素節(jié)點轉(zhuǎn)移到新的數(shù)組中
if (nextTab == null) {
try {
//創(chuàng)建新的數(shù)組,容量是舊數(shù)組的2倍
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
//出現(xiàn)異常時則說明擴(kuò)容失敗,容量大小已經(jīng)超出MAXIMUM_CAPACITY參數(shù)指定的最大的數(shù)組容量
//因為MAXIMUM_CAPACITY參數(shù)指定的容量大小是int類型中正整數(shù)最大的2的次方
//當(dāng)數(shù)組的長度已經(jīng)到達(dá)MAXIMUM_CAPACITY參數(shù)指定的容量大小時,如果再進(jìn)行2倍的擴(kuò)容就會導(dǎo)致數(shù)組的長度變成負(fù)數(shù)
//此時就會導(dǎo)致擴(kuò)容失敗,將int類型中最大的正整數(shù)設(shè)置成擴(kuò)容的閾值來停止本次擴(kuò)容
//如果數(shù)組的長度已到達(dá)數(shù)組最大的容量大小那就不會進(jìn)行擴(kuò)容了
sizeCtl = Integer.MAX_VALUE;
return;
}
//擴(kuò)容后的數(shù)組,當(dāng)其它擴(kuò)容的線程發(fā)現(xiàn)nextTable不為空時則不會重復(fù)擴(kuò)容
nextTable = nextTab;
//擴(kuò)容后,默認(rèn)需要轉(zhuǎn)移整個舊數(shù)組
transferIndex = n;
}
//獲取擴(kuò)容后的數(shù)組長度
int nextn = nextTab.length;
//創(chuàng)建一個轉(zhuǎn)移的節(jié)點
ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);
//是否繼續(xù)轉(zhuǎn)移
boolean advance = true;
//標(biāo)識擴(kuò)容之后舊數(shù)組中的節(jié)點是否全部完成轉(zhuǎn)移
boolean finishing = false;
for (int i = 0, bound = 0; ; ) {
Node<K, V> f;
int fh;
while (advance) {
//nextIndex 當(dāng)前線程開始轉(zhuǎn)移的長度位置
//nextBound 當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的索引位置邊界,如果到達(dá)這個邊界則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點
//轉(zhuǎn)移舊數(shù)組中的元素節(jié)點是從數(shù)組的尾部向數(shù)組的頭部開始轉(zhuǎn)移
int nextIndex, nextBound;
//每次轉(zhuǎn)移完一個索引位置上的節(jié)點的時候都會校驗一下下一次轉(zhuǎn)移的索引位置是否已經(jīng)超出邊界
//或舊數(shù)組中的元素節(jié)點都已經(jīng)全部轉(zhuǎn)移完成
//如果下一次轉(zhuǎn)移的索引位置超出邊界,但是剩下需要轉(zhuǎn)移的節(jié)點沒有被其它線程協(xié)助轉(zhuǎn)移
//那么當(dāng)前線程則繼續(xù)選擇部分的索引長度來轉(zhuǎn)移
if (--i >= bound || finishing)
advance = false;
//校驗剩余需要轉(zhuǎn)移的索引長度是否為0,如果為0則說明舊數(shù)組中沒有需要轉(zhuǎn)移的元素節(jié)點了
else if ((nextIndex = transferIndex) <= 0) {
//將轉(zhuǎn)移的節(jié)點的索引位置設(shè)置為-1,后續(xù)會根據(jù)該條件退出擴(kuò)容方法
i = -1;
advance = false;
//根據(jù)開始轉(zhuǎn)移的長度位置與每個線程需要轉(zhuǎn)移的長度進(jìn)行比較
//如果開始轉(zhuǎn)移的長度位置大于每個線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個線程需要轉(zhuǎn)移的長度
//獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置邊界
//如果開始轉(zhuǎn)移的長度位置小于等于每個線程需要轉(zhuǎn)移的長度,那就說明當(dāng)前需要轉(zhuǎn)移的長度不需要其它線程協(xié)助
//當(dāng)前線程則會將剩下需要轉(zhuǎn)移的節(jié)點轉(zhuǎn)移到新的數(shù)組中
} else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
//將計算后的索引邊界賦予bound,用于線程每次轉(zhuǎn)移之后進(jìn)行校驗
//如果已經(jīng)到達(dá)了邊界說明線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點轉(zhuǎn)移
//線程不再執(zhí)行節(jié)點的轉(zhuǎn)移
bound = nextBound;
//開始轉(zhuǎn)移的長度位置-1,獲取到開始轉(zhuǎn)移的節(jié)點的索引位置
i = nextIndex - 1;
advance = false;
}
}
//i < 0 小于0則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的節(jié)點已經(jīng)完成并且沒有其它需要轉(zhuǎn)移的節(jié)點了
//i >= n
//i + n >= nextn
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//當(dāng)前finishing為true時則說明舊數(shù)組中的所有節(jié)點都已經(jīng)轉(zhuǎn)移
//此時就需要將新數(shù)組設(shè)置為當(dāng)前集合使用的數(shù)組并計算下一次的擴(kuò)容閾值
if (finishing) {
//將擴(kuò)容的數(shù)組置為空,代表當(dāng)前沒有線程正在進(jìn)行擴(kuò)容操作
nextTable = null;
//將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組
table = nextTab;
//計算下一次擴(kuò)容的閾值
sizeCtl = (n << 1) - (n >>> 1);
//擴(kuò)容完成,退出
return;
}
//finishing為false則會走到當(dāng)前語句,則說明當(dāng)前線程并不知道舊數(shù)組中的元素節(jié)點有沒有轉(zhuǎn)移完成
//但是當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的索引節(jié)點已經(jīng)完成,此時就需要將并發(fā)擴(kuò)容中的線程數(shù)量減1
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//擴(kuò)容標(biāo)記左移16位獲取到線程數(shù)量的標(biāo)記
//使用sc-2獲取到當(dāng)前還剩下的線程數(shù)量的標(biāo)記
//如果兩個相等則說明當(dāng)前線程是最后一個擴(kuò)容結(jié)束的線程
//此時就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點遍歷來檢查是否所有的元素節(jié)點都轉(zhuǎn)移了
//如果都轉(zhuǎn)移了,則將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組,并且計算下一次擴(kuò)容的閾值
//如果還有元素節(jié)點沒有轉(zhuǎn)移,當(dāng)前線程則會將剩下的元素節(jié)點進(jìn)行轉(zhuǎn)移
//如果兩個不相等則說明當(dāng)前線程不是最后一個擴(kuò)容結(jié)束的線程
//當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點,并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點了,當(dāng)前線程直接退出擴(kuò)容
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
//當(dāng)前線程是最后一個結(jié)束擴(kuò)容的線程,此時就需要檢查舊數(shù)組中是否所有的元素節(jié)點都轉(zhuǎn)移了
finishing = advance = true;
//從舊數(shù)組的尾部開始檢查
i = n;
}
} else if ((f = tabAt(tab, i)) == null)
//如果指定索引位置上的節(jié)點為空則直接將舊數(shù)組中該索引位置上的節(jié)點設(shè)置成一個正在轉(zhuǎn)移的節(jié)點進(jìn)行占位
//當(dāng)有新的線程要對該索引位置的節(jié)點操作的時候則會發(fā)現(xiàn)該索引位置上的節(jié)點是一個正在轉(zhuǎn)移的節(jié)點
//則不會對該索引位置上的節(jié)點進(jìn)行操作而是先協(xié)助擴(kuò)容線程進(jìn)行轉(zhuǎn)移
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
//指定索引位置上的節(jié)點已經(jīng)被其它線程處理過
advance = true;
else {
//加鎖,防止其它線程對當(dāng)前需要移動的節(jié)點進(jìn)行操作
synchronized (f) {
//校驗節(jié)點是否變更
if (tabAt(tab, i) == f) {
//ln 低位節(jié)點,該節(jié)點放置到新數(shù)組中的索引位置與在舊數(shù)組中的索引位置相同
//hn 高位節(jié)點,該節(jié)點放置到新數(shù)組中的索引位置是在舊數(shù)組中的索引位置加上舊數(shù)組的長度
Node<K, V> ln, hn;
//校驗節(jié)點的hash值是否大于等于0
//如果節(jié)點的hash值大于等于0則說明該索引位置上只有一個節(jié)點或節(jié)點是一個鏈表節(jié)點
if (fh >= 0) {
//使用節(jié)點的hash值與舊數(shù)組的長度進(jìn)行與運(yùn)算
//與運(yùn)算后的結(jié)果分為0和1,0則將該節(jié)點放置到低位,1則將節(jié)點放置高位
int runBit = fh & n;
//避免后續(xù)轉(zhuǎn)移節(jié)點的時候沒有必要的循環(huán)以及創(chuàng)建節(jié)點
Node<K, V> lastRun = f;
//遍歷整個鏈表來決定從那個節(jié)點開始以及后續(xù)的節(jié)點都是沒有必要遍歷和創(chuàng)建的
//而是直接使用指針指向舊數(shù)組中的節(jié)點,當(dāng)擴(kuò)容完成之后舊數(shù)組以及舊數(shù)組中部分沒有被指針引用的節(jié)點則會被回收
for (Node<K, V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
//沒有必要循環(huán)和創(chuàng)建的節(jié)點應(yīng)該放置到新數(shù)組中的低為還是高位
if (runBit == 0) {
//低位
ln = lastRun;
hn = null;
} else {
//高位
hn = lastRun;
ln = null;
}
//從頭節(jié)點開始遍歷將需要重新創(chuàng)建的節(jié)點進(jìn)行創(chuàng)建并添加到高位或低位
//在添加到高位或低位的時候,新創(chuàng)建的節(jié)點使用的是頭插法
//如果有節(jié)點不需要重新創(chuàng)建,那不需要重新創(chuàng)建的節(jié)點則會放到高位或低位節(jié)點的尾部
//在上面的時候如果不需要重新創(chuàng)建的節(jié)點其實就已經(jīng)放在了ln或hn中
//當(dāng)有節(jié)點重新創(chuàng)建了的時候,則ln或hn設(shè)置為新創(chuàng)建的節(jié)點的下一個節(jié)點
//其實整體來說就是頭插法,如果說整個鏈表或部分連續(xù)的鏈表節(jié)點不需要重新創(chuàng)建的時候還是保持在舊數(shù)組中一樣的順序
for (Node<K, V> p = f; p != lastRun; p = p.next) {
int ph = p.hash;
K pk = p.key;
V pv = p.val;
if ((ph & n) == 0)
//低位
ln = new Node<K, V>(ph, pk, pv, ln);
else
//高位
hn = new Node<K, V>(ph, pk, pv, hn);
}
//將低位節(jié)點鏈表添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同
setTabAt(nextTab, i, ln);
//將高位節(jié)點鏈表添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度
setTabAt(nextTab, i + n, hn);
//將舊數(shù)組中的索引位置上的節(jié)點設(shè)置為一個已經(jīng)轉(zhuǎn)移的節(jié)點
setTabAt(tab, i, fwd);
//繼續(xù)推進(jìn)下一次節(jié)點轉(zhuǎn)移
advance = true;
//節(jié)點是一個紅黑樹節(jié)點
} else if (f instanceof TreeBin) {
TreeBin<K, V> t = (TreeBin<K, V>) f;
//低位頭節(jié)點和尾節(jié)點
TreeNode<K, V> lo = null, loTail = null;
//高位頭節(jié)點和尾節(jié)點
TreeNode<K, V> hi = null, hiTail = null;
//低位節(jié)點和高位節(jié)點的數(shù)量
int lc = 0, hc = 0;
//從TreeBin對象中記錄的鏈表的頭節(jié)點開始遍歷將每一個節(jié)點分為低位節(jié)點和高位節(jié)點
for (Node<K, V> e = t.first; e != null; e = e.next) {
int h = e.hash;
//構(gòu)造新的樹節(jié)點
TreeNode<K, V> p = new TreeNode<K, V>(h, e.key, e.val, null, null);
if ((h & n) == 0) {
//將構(gòu)造的樹節(jié)點的上一個節(jié)點指針指向原低位尾節(jié)點
//如果原低尾節(jié)點為空則說明當(dāng)前樹節(jié)點是第一個節(jié)點
//那就將當(dāng)前樹節(jié)點設(shè)置為低位頭節(jié)點
if ((p.prev = loTail) == null)
lo = p;
else
//原低位尾節(jié)點不為空則將原尾節(jié)點的下一個節(jié)點指針指向當(dāng)前樹節(jié)點
loTail.next = p;
//將新構(gòu)造的樹節(jié)點設(shè)置為低位尾節(jié)點
loTail = p;
//低位節(jié)點的數(shù)量自增
++lc;
} else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
//拆分后的高低位節(jié)點的數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值
//如果小于等于則調(diào)用untreeify方法將樹節(jié)點轉(zhuǎn)換為鏈表節(jié)點
//如果高位節(jié)點數(shù)量或低位節(jié)點數(shù)量大于閾值則會校驗對方節(jié)點的數(shù)量是否不等于0
//如果說對方的節(jié)點數(shù)量等于0則說明節(jié)點并沒有拆分為高位或低位節(jié)點
//那就使用原本的TreeBin對象進(jìn)行轉(zhuǎn)移
//如果對方的節(jié)點數(shù)量大于0則說明已經(jīng)拆分為了高低位節(jié)點
//此時就需要將高低位節(jié)點轉(zhuǎn)換為紅黑樹并封裝成一個TreeBin對象
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K, V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K, V>(hi) : t;
//將低位節(jié)點TreeBin對象添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同
setTabAt(nextTab, i, ln);
//將高位節(jié)點TreeBin添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度
setTabAt(nextTab, i + n, hn);
//將舊數(shù)組中的索引位置上的節(jié)點設(shè)置為一個已經(jīng)轉(zhuǎn)移的節(jié)點
setTabAt(tab, i, fwd);
//繼續(xù)推進(jìn)下一次節(jié)點轉(zhuǎn)移
advance = true;
}
}
}
}
}
}
transfer方法中的代碼比較多,我們就一段一段的講解。
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
if (nextTab == null) {
try {
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
首先會通過舊數(shù)組的長度來計算每個cpu在轉(zhuǎn)移舊數(shù)組中的節(jié)點所需要負(fù)責(zé)的區(qū)域長度,每個cpu最少需要負(fù)責(zé)16個區(qū)域的長度。 nextTab == null則是校驗是否有線程已經(jīng)在對新的數(shù)組進(jìn)行初始化了,如果沒有那當(dāng)前線程則會去初始化新的數(shù)組,新數(shù)組的長度則是舊數(shù)組的2倍,如果出現(xiàn)異常則說明舊數(shù)組的長度已經(jīng)到達(dá)了數(shù)組最大的容量大小了,此時就不能再繼續(xù)擴(kuò)容了,數(shù)組最大的容量大小為2的30次方,則是int類型中正整數(shù)最大的2的次方,如果再次進(jìn)行擴(kuò)容的話就會導(dǎo)致數(shù)組的容量大小變成負(fù)數(shù)。
ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
ForwardingNode節(jié)點是一個占位節(jié)點,當(dāng)將舊數(shù)組中指定索引位置上的所有節(jié)點都轉(zhuǎn)移到了新的數(shù)組中,則會使用該節(jié)點進(jìn)行占位,告知其它線程該索引位置上的節(jié)點都已經(jīng)被轉(zhuǎn)移到了新的數(shù)組中。
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
} else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
while循環(huán)的作用主要是為每個線程分配所負(fù)責(zé)的區(qū)域以及推進(jìn)轉(zhuǎn)移的進(jìn)度。
--i >= bound || finishing:--i >= bound則是每次轉(zhuǎn)移完一個索引位置上的節(jié)點的時候都會校驗一下下一次轉(zhuǎn)移的索引位置的節(jié)點是否已經(jīng)超出當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的邊界了,而finishing則是校驗舊數(shù)組中所有的元素節(jié)點是否都已經(jīng)完成了轉(zhuǎn)移。
(nextIndex = transferIndex) <= 0:剩余需要轉(zhuǎn)移的區(qū)域的長度是否小于等于0,小于等于0則說明舊數(shù)組中的元素節(jié)點已經(jīng)轉(zhuǎn)移完成了,在開始準(zhǔn)備轉(zhuǎn)移的時候該值為舊數(shù)組的長度,每當(dāng)有一個線程來協(xié)助擴(kuò)容的時候則會從該值中取一部分長度來負(fù)責(zé)轉(zhuǎn)移。
條件三則是根據(jù)開始轉(zhuǎn)移的長度位置與每個線程需要轉(zhuǎn)移的長度進(jìn)行比較,如果開始轉(zhuǎn)移的長度位置大于每個線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個線程需要轉(zhuǎn)移的長度,獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置的邊界,再將剩余需要轉(zhuǎn)移的長度存放到transferIndex中,等待其它線程協(xié)助或者說等待當(dāng)前線程轉(zhuǎn)移完所負(fù)責(zé)的區(qū)域之后繼續(xù)轉(zhuǎn)移剩余的長度,如果開始轉(zhuǎn)移的長度位置小于等于每個線程所需要轉(zhuǎn)移的長度,那就說明當(dāng)前線程自己可以完成轉(zhuǎn)移,不需要其它線程協(xié)助,轉(zhuǎn)移節(jié)點的時候則是從數(shù)組的尾部向前推進(jìn)。
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n;
}
}
i小于0則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的區(qū)域節(jié)點已經(jīng)轉(zhuǎn)移完成,并且數(shù)組中沒有其它需要轉(zhuǎn)移的節(jié)點了,此時就需要通過下面的cas操作將sizeCtl中的擴(kuò)容線程數(shù)量減1,線程數(shù)量減1之后會去校驗當(dāng)前線程是否是最后一個擴(kuò)容的線程,如果不是則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點轉(zhuǎn)移,并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點了,當(dāng)前線程直接退出擴(kuò)容,如果是最后一個擴(kuò)容的線程,此時就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點變量來檢查是否所有的元素節(jié)點都轉(zhuǎn)移了,如果還有沒被轉(zhuǎn)移的元素節(jié)點,那當(dāng)前線程則會去進(jìn)行轉(zhuǎn)移,當(dāng)檢查完成之后會將新的數(shù)組替換舊的數(shù)組,并計算下一次擴(kuò)容的閾值。
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);

如果指定索引位置上的節(jié)點為空則通過cas操作將舊數(shù)組中該索引位置上的節(jié)點設(shè)置成ForwardingNode節(jié)點進(jìn)行占位,告知其它線程該索引位置上的節(jié)點已經(jīng)進(jìn)行了轉(zhuǎn)移,其它線程則不會對該索引位置上的節(jié)點進(jìn)行操作,而是先協(xié)助擴(kuò)容線程進(jìn)行節(jié)點轉(zhuǎn)移。
else if ((fh = f.hash) == MOVED)
advance = true;
索引位置上的節(jié)點的hash值為MOVED,則說明該索引位置上的節(jié)點都已經(jīng)轉(zhuǎn)移了,當(dāng)前線程則繼續(xù)推進(jìn)索引位置轉(zhuǎn)節(jié)點。
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K, V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K, V> lastRun = f;
for (Node<K, V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
} else {
hn = lastRun;
ln = null;
}
for (Node<K, V> p = f; p != lastRun; p = p.next) {
int ph = p.hash;
K pk = p.key;
V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K, V>(ph, pk, pv, ln);
else
hn = new Node<K, V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
} else if (f instanceof TreeBin) {
TreeBin<K, V> t = (TreeBin<K, V>) f;
TreeNode<K, V> lo = null, loTail = null;
TreeNode<K, V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K, V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K, V> p = new TreeNode<K, V>(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
} else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K, V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K, V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
我們再看真正轉(zhuǎn)移節(jié)點的代碼,分為轉(zhuǎn)移鏈表節(jié)點和轉(zhuǎn)移紅黑樹節(jié)點。
轉(zhuǎn)移鏈表節(jié)點:鏈表節(jié)點會被分為高位節(jié)點鏈表(hn)和低位節(jié)點鏈表(ln),首先通過頭節(jié)點的hash值與舊數(shù)組的長度進(jìn)行與運(yùn)算,與運(yùn)算后的結(jié)果分為0和1,0則將節(jié)點放置到低位,1將該節(jié)點放置到高位,然后遍歷整個鏈表來決定從那個節(jié)點開始以及后續(xù)的節(jié)點都是沒有必要重新創(chuàng)建的,而是直接使用指針指向舊數(shù)組中的節(jié)點,lastRun指針指向的節(jié)點以及后續(xù)的節(jié)點都是沒有必要創(chuàng)建的,因為你后續(xù)的鏈表節(jié)點沒有被拆分為高位或低位,是一個連續(xù)存放在高位或低位的鏈表節(jié)點,所以說不需要重新創(chuàng)建節(jié)點,比如說一個鏈表中的所有節(jié)點都是放在低位節(jié)點中的,那lastRun指針指向的就是鏈表的頭節(jié)點,該鏈表中的節(jié)點并沒有改動,并不需要重新創(chuàng)建節(jié)點,而是直接將鏈表中的頭節(jié)點放置到新的數(shù)組中即可,當(dāng)區(qū)分了哪些節(jié)點是不需要重新創(chuàng)建的,則會將不需要重新創(chuàng)建的節(jié)點的頭節(jié)點lastRun賦值給高位或低位,然后從鏈表的頭節(jié)點開始遍歷將需要創(chuàng)建的節(jié)點進(jìn)行創(chuàng)建并添加到高位鏈表或低位鏈表中,在添加到高位鏈表或低位鏈表的時候,節(jié)點使用的是頭插法,創(chuàng)建完成之后,低位節(jié)點鏈表會放置到新數(shù)組中所在的索引位置與節(jié)點在舊數(shù)組中所在的索引位置相同,高位節(jié)點鏈表會放置到新數(shù)組中所在的索引位置是節(jié)點在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度,當(dāng)高低位節(jié)點鏈表都轉(zhuǎn)移完成之后則會在舊數(shù)組中該索引位置上添加到一個占位節(jié)點。
下面的圖中ln則是低位節(jié)點鏈表,hn則是高位節(jié)點鏈表,在某些情況下有些節(jié)點并不需要重新創(chuàng)建,而是使用原來的節(jié)點,最差的情況下就是只有鏈表節(jié)點的尾部的一個節(jié)點不需要重新創(chuàng)建。



紅黑樹節(jié)點:從TreeBin對象中記錄的鏈表的頭節(jié)點開始遍歷,將每一個節(jié)點轉(zhuǎn)換為新的樹節(jié)點,并分為高低位鏈表節(jié)點,校驗高低位鏈表節(jié)點中的節(jié)點數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值,如果小于等于則會將高低位鏈表節(jié)點中的所有樹節(jié)點都轉(zhuǎn)換為普通的Node節(jié)點,如果不小于等于則會將高低位鏈表節(jié)點轉(zhuǎn)換為紅黑樹。 如果說高位或低位是一個鏈表節(jié)點的話,則會將鏈表的頭節(jié)點放置到新的數(shù)組中,如果是紅黑樹的話,則會將TreeBin對象放置到新的數(shù)組中,然后再將舊數(shù)組中的索引位置上添加一個占位節(jié)點。
注意:其實在轉(zhuǎn)移舊數(shù)組中的節(jié)點的時候是有問題的,有可能會造成節(jié)點數(shù)據(jù)的丟失
線程A獲取到了索引位置上的鏈表節(jié)點,頭節(jié)點類型為Node,準(zhǔn)備對該鏈表節(jié)點加synchronized鎖進(jìn)行轉(zhuǎn)移時,此時線程B先加了synchronized鎖,對該索引位置上的鏈表節(jié)點添加了新的節(jié)點并將鏈表節(jié)點轉(zhuǎn)換為了紅黑樹,并將TreeBin對象放置在了該索引位置上,當(dāng)線程B釋放了鎖之后,線程A獲取到了鎖后去判斷之前獲取到的頭節(jié)點Node是否與索引位置上最新的TreeBin對象相同,顯然是不相同的,當(dāng)不相同的情況下就會跳過該索引位置上的節(jié)點的轉(zhuǎn)移,在上面說過,當(dāng)擴(kuò)容完成時,最后一個線程會去檢查舊數(shù)組中是否還有節(jié)點沒有轉(zhuǎn)移,如果有則會進(jìn)行轉(zhuǎn)移,如果說在檢查轉(zhuǎn)移的時候也遇到了上面類似的問題,刪除節(jié)點的時候?qū)?code>TreeBin轉(zhuǎn)換為Node,是不是就會跳過該索引位置上的檢查,從而導(dǎo)致節(jié)點數(shù)據(jù)丟失。
以上就是Java并發(fā)源碼分析ConcurrentHashMap線程集合的詳細(xì)內(nèi)容,更多關(guān)于Java ConcurrentHashMap的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gradle使用maven-publish發(fā)布jar包上傳到私有maven配置
這篇文章主要介紹了gradle使用maven-publish發(fā)布jar包上傳到私有maven的配置示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實例的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10
Java中Date與String相互轉(zhuǎn)換的方法
這篇文章主要為大家詳細(xì)介紹了Java中Date與String相互轉(zhuǎn)換方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10
Maven項目部署到Jboss出現(xiàn)Failed to create a new SAX parser
這篇文章主要為大家詳細(xì)介紹了Maven項目部署到Jboss出現(xiàn)Failed to create a new SAX parser的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
新版IDEA使用Spring Initializr創(chuàng)建工程的兩種方法
這篇文章主要介紹了新版IDEA使用Spring Initializr創(chuàng)建工程(兩種方法,官方工具和IDEA),文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2024-10-10

