解析ConcurrentHashMap: put方法源碼分析
put()方法是并發(fā)HashMap源碼分析的重點(diǎn)方法,這里涉及到并發(fā)擴(kuò)容,桶位尋址等等…- JDK1.8 ConcurrentHashMap結(jié)構(gòu)圖:
1、put方法源碼解析
// 向并發(fā)Map中put一個數(shù)據(jù)
public V put(K key, V value) {
return putVal(key, value, false);
}
// 向并發(fā)Map中put一個數(shù)據(jù)
// Key: 數(shù)據(jù)的鍵
// value:數(shù)據(jù)的值
// onlyIfAbsent:是否替換數(shù)據(jù):
// 如果為false,則當(dāng)put數(shù)據(jù)時,遇到Map中有相同K,V的數(shù)據(jù),則將其替換
// 如果為true,則當(dāng)put數(shù)據(jù)時,遇到Map中有相同K,V的數(shù)據(jù),則不做替換,不插入
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 控制k 和 v 不能為null
if (key == null || value == null) throw new NullPointerException();
// 通過spread方法,可以讓高位也能參與進(jìn)尋址運(yùn)算,使最終得到的hash值更分散
int hash = spread(key.hashCode());
// binCount表示當(dāng)前k-v 封裝成node插入到指定桶位后,在桶位中的所屬鏈表的下標(biāo)位置
// =0 表示當(dāng)前桶位為null,node可以直接放入
// >0 表示當(dāng)前桶位中有節(jié)點(diǎn),遍歷鏈表,將封裝k-v的新node放入鏈表末尾,并記錄鏈表末尾的下標(biāo)binCount
// =2 表示當(dāng)前桶位**可能**已經(jīng)樹化為紅黑樹了
int binCount = 0;
// tab 引用map對象的table
// 自旋
for (Node<K,V>[] tab = table;;) {
// f 表示桶位的頭結(jié)點(diǎn)
// n 表示散列表數(shù)組的長度
// i 表示key通過尋址計算后,得到的桶位下標(biāo)
// fh 表示桶位頭結(jié)點(diǎn)的hash值
Node<K,V> f; int n, i, fh;
// -----------------------------------------------------------------------------
// CASE1:成立,表示當(dāng)前map中的table尚未初始化...
if (tab == null || (n = tab.length) == 0)
// 對map中的table進(jìn)行初始化
tab = initTable();
// -----------------------------------------------------------------------------
// CASE2:table已經(jīng)初始化,此時根據(jù)尋址算法確定一個桶,并且桶的頭結(jié)點(diǎn)f為null
// i 表示key使用路由尋址算法得到key對應(yīng)table數(shù)組的下標(biāo)位置,tabAt方法獲取指定桶位i的頭結(jié)點(diǎn)f
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 這時候就可以將封裝k-v的結(jié)點(diǎn)直接放入桶
// casTabAt通過CAS的方式去向Node數(shù)組指定位置i設(shè)置節(jié)點(diǎn)值,設(shè)置成功返回true 否則返回false
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;
}
// -----------------------------------------------------------------------------
// CASE3:table已經(jīng)初始化,尋址得到的桶位中的頭結(jié)點(diǎn)f不是null,如果該頭結(jié)點(diǎn)f的hash值fh=-1:
// 則,表示當(dāng)前節(jié)點(diǎn)是FWD(forwarding)節(jié)點(diǎn)
// 如果CASE3條件成立:表示當(dāng)前桶位的頭結(jié)點(diǎn)為 FWD結(jié)點(diǎn),表示目前map正處于擴(kuò)容過程中..
else if ((fh = f.hash) == MOVED)
// 發(fā)現(xiàn)f是FWD結(jié)點(diǎn)后,當(dāng)前結(jié)點(diǎn)有義務(wù)去幫助當(dāng)前map對象完成數(shù)據(jù)遷移工作...
// 等學(xué)完擴(kuò)容再來分析這里~
tab = helpTransfer(tab, f);
// -----------------------------------------------------------------------------
// CASE4:CASE1-3都不滿足時,那么當(dāng)前桶位存放的可能是鏈表也可能是紅黑樹代理結(jié)點(diǎn)TreeBin
else {
// 保留替換之前的數(shù)據(jù)引用:當(dāng)新插入的key存在后,會將舊值賦給OldVal,返回給put方法調(diào)用
V oldVal = null;
// 使用synchronized加鎖“頭節(jié)點(diǎn)”(**理論上是“頭結(jié)點(diǎn)”**)
synchronized (f) {
// -----------------------------------------------------------------------
// CASE5:tabAt(tab, i) == f
// 對比一下當(dāng)前桶位的頭節(jié)點(diǎn)是否為之前獲取的頭結(jié)點(diǎn):為什么又要對比一下?
// 如果其它線程在當(dāng)前線程之前將該桶位的頭結(jié)點(diǎn)修改掉了,當(dāng)前線程再使用sync對該節(jié)點(diǎn)f加鎖就有問題了(鎖本身加錯了地方~)
if (tabAt(tab, i) == f) {// 如果條件成立,說明加鎖的對象f沒有問題,持有鎖!
// ------------------------------------------------------------------
// CASE6:fh >= 0)
// 如果條件成立,說明當(dāng)前桶位就是普通鏈表桶位,這里回顧下常量屬性字段:
// static final int MOVED = -1; 表示當(dāng)前節(jié)點(diǎn)是FWD(forwarding)節(jié)點(diǎn)
// static final int TREEBIN = -2; 表示當(dāng)前節(jié)點(diǎn)已經(jīng)樹化
if (fh >= 0) {
// 1.當(dāng)前插入key與鏈表當(dāng)中所有元素的key都不一致時,當(dāng)前的插入操作是追加到鏈表的末尾,binCount此時表示鏈表長度
// 2.當(dāng)前插入key與鏈表當(dāng)中的某個元素的key一致時,當(dāng)前插入操作可能就是替換了。binCount表示沖突位置(binCount - 1)
binCount = 1;
// 迭代循環(huán)當(dāng)前桶位的鏈表,e是每次循環(huán)處理節(jié)點(diǎn)。
for (Node<K,V> e = f;; ++binCount) {
// 當(dāng)前循環(huán)遍歷節(jié)點(diǎn)的key
K ek;
// --------------------------------------------------------
// CASE7:
// 條件一:e.hash == hash
// 成立:表示循環(huán)的當(dāng)前元素的hash值與插入節(jié)點(diǎn)的hash值一致,需要進(jìn)一步判斷
// 條件二:((ek = e.key) == key ||(ek != null && key.equals(ek)))
// 成立:說明循環(huán)的當(dāng)前節(jié)點(diǎn)與插入節(jié)點(diǎn)的key一致,確實(shí)發(fā)生沖突了~
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 將當(dāng)前循環(huán)的元素的值賦值給oldVal
oldVal = e.val;
// 傳入的putVal方法參數(shù)onlyIfAbsent:是否替換數(shù)據(jù):
// false,當(dāng)put數(shù)據(jù)時,遇到Map中有相同K,V的數(shù)據(jù),則將其替換
// true,當(dāng)put數(shù)據(jù)時,遇到Map中有相同K,V的數(shù)據(jù),則不做替換,不插入
if (!onlyIfAbsent)
e.val = value;
break;
}
// --------------------------------------------------------
// CASE8:
// 當(dāng)前元素與插入元素的key不一致時,會走下面程序:
// 1.更新循環(huán)遍歷的節(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)的下一個節(jié)點(diǎn)
// 2.判斷下一個節(jié)點(diǎn)是否為null,如果是null,說明當(dāng)前節(jié)點(diǎn)已經(jīng)是隊尾了,插入數(shù)據(jù)需要追加到隊尾節(jié)點(diǎn)的后面。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// ------------------------------------------------------------------
// CASE9:f instanceof TreeBin
// 如果條件成立,表示當(dāng)前桶位是紅黑樹代理結(jié)點(diǎn)TreeBin
//(前置條件:該桶位一定不是鏈表)
else if (f instanceof TreeBin) {
// p表示紅黑樹中如果與你插入節(jié)點(diǎn)的key 有沖突節(jié)點(diǎn)的話,則putTreeVal方法會返回沖突節(jié)點(diǎn)的引用。
Node<K,V> p;
// 強(qiáng)制設(shè)置binCount為2,因?yàn)閎inCount <= 1 時有其它含義,所以這里設(shè)置為了2 (回頭講addCount的時候會詳細(xì)介紹)
binCount = 2;
// 條件一成立,說明當(dāng)前插入節(jié)點(diǎn)的key與紅黑樹中的某個節(jié)點(diǎn)的key一致,沖突了:
// putTreeVal向紅黑樹中插入結(jié)點(diǎn),插入成功返回null,否則返回沖突結(jié)點(diǎn)對象
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
// 將沖突節(jié)點(diǎn)的值賦值給oldVal
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// ------------------------------------------------------------------
// CASE10:binCount != 0
// 說明當(dāng)前桶位不為null,可能是紅黑樹也可能是鏈表
if (binCount != 0) {
// 如果binCount>=8 表示處理的桶位一定是鏈表
if (binCount >= TREEIFY_THRESHOLD)
// 因?yàn)橥爸墟湵黹L度大于了8,需要樹化:
// 調(diào)用轉(zhuǎn)化鏈表為紅黑樹的方法
treeifyBin(tab, i);
// 說明當(dāng)前線程插入的數(shù)據(jù)key,與原有k-v發(fā)生沖突,需要將原數(shù)據(jù)v返回給調(diào)用者。
if (oldVal != null)
return oldVal;
break;
}
}
}
// Map中的元素數(shù)據(jù)量累加方法:有額外的以下功能
// 1.統(tǒng)計當(dāng)前table一共有多少數(shù)據(jù)
// 2.并判斷是否達(dá)到擴(kuò)容閾值標(biāo)準(zhǔn),觸發(fā)擴(kuò)容。
addCount(1L, binCount);
return null;
}
2、initTable方法源碼分析
第一次放元素時,初始化桶數(shù)組:
/**
* table初始化
*/
private final Node<K,V>[] initTable() {
// tab: 引用map.table
// sc: sizeCtl的臨時值
// sizeCtl:默認(rèn)為0,用來控制table的狀態(tài)、以及初始化和擴(kuò)容操作:
// sizeCtl<0表示table的狀態(tài):
//(1)=-1,表示有其他線程正在進(jìn)行初始化操作。(其他線程就不能再進(jìn)行初始化,相當(dāng)于一把鎖)
//(2)=-(1 + nThreads),表示有n個線程正在一起擴(kuò)容。
// sizeCtl>=0表示table的初始化和擴(kuò)容相關(guān)操作:
//(3)=0,默認(rèn)值,后續(xù)在真正初始化table的時候使用,設(shè)置為默認(rèn)容量DEFAULT_CAPACITY --> 16。
//(4)>0,將sizeCtl設(shè)置為table初始容量或擴(kuò)容完成后的下一次擴(kuò)容的門檻。
Node<K,V>[] tab; int sc;
// 附加條件的自旋: 條件是map.table尚未初始化...
while ((tab = table) == null || tab.length == 0) {
// -----------------------------------------------------------------------------
// CASE1: sizeCtl) < 0
// sizeCtl < 0可能是以下2種情況:
//(1)-1,表示有其他線程正在進(jìn)行table初始化操作。
//(2)-(1 + nThreads),表示有n個線程正在一起擴(kuò)容。
if ((sc = sizeCtl) < 0)
// 這里sizeCtl大概率就是-1,表示其它線程正在進(jìn)行創(chuàng)建table的過程,當(dāng)前線程沒有競爭到初始化table的鎖,進(jìn)而當(dāng)前線程被迫等待...
Thread.yield();
// -----------------------------------------------------------------------------
// CASE2:sizeCtl) >= 0 且U.compareAndSwapInt(this, SIZECTL, sc, -1)結(jié)果為true
// U.compareAndSwapInt(this, SIZECTL, sc, -1):以CAS的方式修改當(dāng)前線程的sizeCtl為-1,
// sizeCtl如果成功被修改為-1,就返回true,否則返回false。
// 當(dāng)返回true時,則該線程就可以進(jìn)入下面的else if代碼塊中,這時候sizeCtl=-1相當(dāng)于是一把鎖,表示下面的else if代碼塊已經(jīng)被該線程占用,其他線程不能再進(jìn)入~
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// ----------------------------------------------------------------------
// CASE3: 這里為什么又要判斷呢?
// 為了防止其它線程已經(jīng)初始化table完畢了,然后當(dāng)前線程再次對其初始化..導(dǎo)致丟失數(shù)據(jù)。
// 如果條件成立,說明其它線程都沒有進(jìn)入過這個if塊,當(dāng)前線程就是具備初始化table權(quán)利了。
if ((tab = table) == null || tab.length == 0) {
// sc大于等于0的情況如下:
//(3)=0,默認(rèn)值,后續(xù)在真正初始化table的時候使用,設(shè)置為默認(rèn)容量DEFAULT_CAPACITY --> 16。
//(4)>0,將sizeCtl設(shè)置為table初始容量或擴(kuò)容完成后的下一次擴(kuò)容的門檻。
// 如果sc大于0,則創(chuàng)建table時使用sc為指定table初始容量大小,
// 否則使用16默認(rèn)值DEFAULT_CAPACITY
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
// 創(chuàng)建新數(shù)組nt
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
// 將新數(shù)組nt賦值給table、tab
table = tab = nt;
// sc設(shè)置為下次散列表擴(kuò)容的門檻:0.75n
sc = n - (n >>> 2);
}
} finally {
// 將sc賦值給sizeCtl,分為一下2種情況:
// 1、當(dāng)前線程既通過了CASE2的判斷,也通過了CASE3的判斷:
// 則,當(dāng)前線程是第一次創(chuàng)建map.table的線程,那么,sc就表示下一次擴(kuò)容的閾值。
// 2、當(dāng)線程通過了CASE2的判斷,但是沒有通過CASE3的判斷:
// 則,當(dāng)前線程并不是第一次創(chuàng)建map.table的線程,當(dāng)前線程通過CASE2的判斷時,將
// sizeCtl設(shè)置為了-1 ,那么在該線程結(jié)束上面的代碼邏輯之后,需要將sc修改回進(jìn)入CASE2之前的sc值。
sizeCtl = sc;
}
break;
}
}
return tab;
}
(1)使用CAS鎖控制只有一個線程初始化桶數(shù)組;
(2)sizeCtl在初始化后存儲的是擴(kuò)容門檻;
(3)擴(kuò)容門檻寫死的是桶數(shù)組大小的0.75倍,桶數(shù)組大小即map的容量,也就是最多存儲多少個元素。
3、addCount方法(難點(diǎn))
在閱讀addCount方法源碼之前,最好再去熟悉下LongAdder源碼:一篇帶你解析入門LongAdder源碼
addCount方法作用:每次添加元素后,元素數(shù)量加1,并判斷是否達(dá)到擴(kuò)容門檻,達(dá)到了則進(jìn)行擴(kuò)容或協(xié)助擴(kuò)容。
/**
* Map中的元素數(shù)據(jù)量累加方法:有額外的以下功能
* 1.統(tǒng)計當(dāng)前table一共有多少數(shù)據(jù)
* 2.并判斷是否達(dá)到擴(kuò)容閾值標(biāo)準(zhǔn),觸發(fā)擴(kuò)容
*/
private final void addCount(long x, int check) {
// as 表示 LongAdder.cells
// b 表示LongAdder.base
// s 表示當(dāng)前map.table中元素的數(shù)量
CounterCell[] as; long b, s;
// -------------------------統(tǒng)計當(dāng)前table一共有多少數(shù)據(jù)----------------------------------
// CASE1:
// (as = counterCells) != null
// 條件一:true->表示cells已經(jīng)初始化了,當(dāng)前線程應(yīng)該去使用hash尋址找到合適的cell 去累加數(shù)據(jù)
// false->表示當(dāng)前線程應(yīng)該直接將數(shù)據(jù)累加到base(沒有線程競爭)
// !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)
// 條件二:false->表示寫base成功,數(shù)據(jù)累加到base中了,當(dāng)前競爭不激烈,不需要創(chuàng)建cells
// true->表示寫base失敗,與其他線程在base上發(fā)生了競爭,當(dāng)前線程應(yīng)該去嘗試創(chuàng)建cells。
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
// 有幾種情況進(jìn)入到if塊中?
// 條件一為true->表示cells已經(jīng)初始化了,當(dāng)前線程應(yīng)該去使用hash尋址找到合適的cell去累加數(shù)據(jù)
// 條件二為true->表示寫base失敗,與其他線程在base上發(fā)生了競爭,當(dāng)前線程應(yīng)該去嘗試創(chuàng)建cells。
// a 表示當(dāng)前線程hash尋址命中的cell
CounterCell a;
// v 表示當(dāng)前線程寫cell時的期望值
long v;
// m 表示當(dāng)前cells數(shù)組的長度
int m;
// true -> 未發(fā)生線程競爭 false->發(fā)生線程競爭
boolean uncontended = true;
// ---------------------------------------------------------------------------
// CASE2:
// 條件一:as == null || (m = as.length - 1) < 0
// true-> 表示當(dāng)前線程是通過寫base競爭失?。–ASE1的條件二),然后進(jìn)入的if塊,就需要調(diào)用fullAddCount方法去擴(kuò)容或者重試..
// (fullAddCount方法就類似于LongAdder.longAccumulate方法)
// 條件二:a = as[ThreadLocalRandom.getProbe() & m]) == null 前置條件:cells已經(jīng)初始化了
// true->表示當(dāng)前線程命中的cell表格是個空的,需要當(dāng)前線程進(jìn)入fullAddCount方法去初始化cell,放入當(dāng)前位置.
// 條件三:!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)
// 前置條件:條件二中當(dāng)前線程命中的cell表格不是空的~
// false->取反得到false,表示當(dāng)前線程使用cas方式更新當(dāng)前命中的cell成功
// true->取反得到true,表示當(dāng)前線程使用cas方式更新當(dāng)前命中的cell失敗,需要進(jìn)入fullAddCount進(jìn)行重試或者擴(kuò)容cells。
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
) {
fullAddCount(x, uncontended);
// 考慮到fullAddCount里面的事情比較累,就讓當(dāng)前線程不參與到擴(kuò)容相關(guān)的邏輯了,直接返回到調(diào)用點(diǎn)。
return;
}
if (check <= 1)
return;
// sumCount統(tǒng)計當(dāng)前散列表元素個數(shù)sum = base + cells[0] + ... + cells[n],這是一個期望值
s = sumCount();
}
// -------------------------判斷是否達(dá)到擴(kuò)容閾值標(biāo)準(zhǔn),觸發(fā)擴(kuò)容----------------------------
// CASE3:
// check >= 0表示一定是一個put操作調(diào)用的addCount,check < 0是remove操作調(diào)用的addCount方法
if (check >= 0) {
// tab 表示 map.table
// nt 表示 map.nextTable
// n 表示map.table數(shù)組的長度
// sc 表示sizeCtl的臨時值
Node<K,V>[] tab, nt; int n, sc;
/**
* sizeCtl < 0
* 1. -1 表示當(dāng)前table正在初始化(有線程在創(chuàng)建table數(shù)組),當(dāng)前線程需要自旋等待..
* 2.表示當(dāng)前table數(shù)組正在進(jìn)行擴(kuò)容 ,高16位表示:擴(kuò)容的標(biāo)識戳 低16位表示:(1 + nThread) 當(dāng)前參與并發(fā)擴(kuò)容的線程數(shù)量
*
* sizeCtl = 0,表示創(chuàng)建table數(shù)組時 使用DEFAULT_CAPACITY為大小
*
* sizeCtl > 0
*
* 1. 如果table未初始化,表示初始化大小
* 2. 如果table已經(jīng)初始化,表示下次擴(kuò)容時的 觸發(fā)條件(閾值)
*/
// 有條件自旋~
// 條件一:s >= (long)(sc = sizeCtl)
// true -> 1.當(dāng)前sizeCtl為一個負(fù)數(shù) 表示正在擴(kuò)容中..
// 2.當(dāng)前sizeCtl是一個正數(shù),表示擴(kuò)容閾值,但是s已經(jīng)達(dá)到擴(kuò)容閾值(需要擴(kuò)容)
// false -> 表示當(dāng)前table尚未達(dá)到擴(kuò)容閾值條件(不需要擴(kuò)容)
// 條件二:(tab = table) != null 恒成立 true
// 條件三:(n = tab.length) < MAXIMUM_CAPACITY
// true -> 當(dāng)前table長度小于最大值限制,則可以進(jìn)行擴(kuò)容。
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 獲取擴(kuò)容唯一標(biāo)識戳
// eg: 16 -> 32 擴(kuò)容標(biāo)識戳為:1000 0000 0001 1011
// 什么意思呢?
// 即,所有將map容量由16擴(kuò)容到32的線程,其拿到的擴(kuò)容唯一標(biāo)識戳都是1000 0000 0001 1011
int rs = resizeStamp(n);
// --------------------------------------------------------------------------
// CASE4:
// 條件成立:表示當(dāng)前table正在擴(kuò)容,則,當(dāng)前線程理論上應(yīng)該協(xié)助table完成擴(kuò)容
if (sc < 0) {
// --------------------------------------------------------------
// CASE2: 條件1~4只要有個為true就會跳出循環(huán),不會繼續(xù)擴(kuò)容~
// 條件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
// true -> 說明當(dāng)前線程獲取到的擴(kuò)容唯一標(biāo)識戳 非 本批次擴(kuò)容
// false -> 說明當(dāng)前線程獲取到的擴(kuò)容唯一標(biāo)識戳 是 本批次擴(kuò)容
// 條件二:JDK1.8 中有bug,jira已經(jīng)提出來了,其實(shí)想表達(dá)的是 sc == (rs << 16 ) + 1
// true -> 表示擴(kuò)容完畢,當(dāng)前線程不需要再參與進(jìn)來了
// false -> 擴(kuò)容還在進(jìn)行中,當(dāng)前線程可以參與
// 條件三:JDK1.8 中有bug,jira已經(jīng)提出來了,其實(shí)想表達(dá)的是:
// sc == (rs << 16) + MAX_RESIZERS
// true-> 表示當(dāng)前參與并發(fā)擴(kuò)容的線程達(dá)到了最大值 65535 - 1
// false->表示當(dāng)前線程可以參與進(jìn)來
//條件四:(nt = nextTable) == null
// true -> 表示本次擴(kuò)容結(jié)束
// false -> 擴(kuò)容正在進(jìn)行中
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
// 條件1~4只要有個為true就會跳出循環(huán),不會繼續(xù)擴(kuò)容~
break;
// --------------------------------------------------------------
// CASE5:
// 前置條件:當(dāng)前table正在執(zhí)行擴(kuò)容中(即,CASE4沒有被通過)當(dāng)前線程有機(jī)會參與進(jìn)擴(kuò)容。
// 條件成立:說明當(dāng)前線程成功參與到擴(kuò)容任務(wù)中,并且將sc低16位值加1,表示多了一個線程參與工作~
// 條件失?。?
// 1.當(dāng)前有很多線程都在此處嘗試修改sizeCtl,有其它一個線程修改成功了
// 導(dǎo)致你的sc期望值與內(nèi)存中的值不一致,CAS修改失敗。(下次自選大概率不會在來到這里~)
// 2.transfer任務(wù)內(nèi)部的線程也修改了sizeCtl。
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
// 擴(kuò)容(遷移數(shù)據(jù)):協(xié)助擴(kuò)容線程,持有nextTable參數(shù),進(jìn)入該方法協(xié)助擴(kuò)容~
transfer(tab, nt);
}
// --------------------------------------------------------------------------
// CASE6: 以CAS的方式去更新sc,更新成功返回true,否則返回false
// 條件成立,說明當(dāng)前線程是觸發(fā)擴(kuò)容的**第一個**線程,在transfer方法需要做一些擴(kuò)容準(zhǔn)備工作:
// rs << RESIZE_STAMP_SHIFT:將擴(kuò)容唯一標(biāo)識戳左移16位 eg:
// 1000 0000 0001 1011 << 16 得到 1000 0000 0001 1011 0000 0000 0000 0000
// 緊接這 (rs << RESIZE_STAMP_SHIFT) + 2)操作:
// 1000 0000 0001 1011 0000 0000 0000 0000 + 2
// => 1000 0000 0001 1011 0000 0000 0000 0010,這個二進(jìn)制數(shù)有如下含義:
// sizeCtl的高16位: 1000 0000 0001 1011 表示當(dāng)前的擴(kuò)容標(biāo)識戳
// sizeCtl的低16位: 0000 0000 0000 0010 表示(1 + nThread),即,目前有多少個線程正在參與擴(kuò)容~
// 那么這里的n是怎么來的呢?
// eg: (rs << RESIZE_STAMP_SHIFT) + 2 這里的2,就是1 + 1,后面的1就是對1*Thread,即(1+1*Threads)
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// 擴(kuò)容(遷移數(shù)據(jù)):觸發(fā)擴(kuò)容條件的線程 不持有nextTable
transfer(tab, null);
// 重新計算table中的元素個數(shù)
s = sumCount();
}
}
}
4、總結(jié)
(1)元素個數(shù)的存儲方式類似于LongAdder類,存儲在不同的段上,減少不同線程同時更新size時的沖突;
(2)計算元素個數(shù)時把這些段的值及baseCount相加算出總的元素個數(shù);
(3)正常情況下sizeCtl存儲著擴(kuò)容門檻,擴(kuò)容門檻為容量的0.75倍;
(4)擴(kuò)容時sizeCtl高位存儲擴(kuò)容郵戳(resizeStamp),低位存儲擴(kuò)容線程數(shù)加1(1+nThreads);
(5)其它線程添加元素后如果發(fā)現(xiàn)存在擴(kuò)容,也會加入的擴(kuò)容行列中來;
以上就是ConcurrentHashMap源碼的put存入數(shù)據(jù)的方法以及相關(guān)方法,由于transfer 遷移數(shù)據(jù)這個方法比較復(fù)雜,我們放在下一篇文章中單獨(dú)分析~也希望大家多多關(guān)注腳本之家的其他內(nèi)容!
相關(guān)文章
Java中List轉(zhuǎn)Map的幾種常見方式與對比
JavaList轉(zhuǎn)Map是一個非常常用的技術(shù),對于Java開發(fā)人員來講,掌握該技術(shù)可以幫助我們更加高效地操作List集合中的對象,這篇文章主要給大家介紹了關(guān)于Java中List轉(zhuǎn)Map的幾種常見方式與對比的相關(guān)資料,需要的朋友可以參考下2024-02-02
MyBatis-Plus中MetaObjectHandler沒生效完美解決
在進(jìn)行測試時發(fā)現(xiàn)配置的MyMetaObjectHandler并沒有生效,本文主要介紹了MyBatis-Plus中MetaObjectHandler沒生效完美解決,具有一定的參考價值,感興趣的可以了解一下2023-11-11
詳解springboot 使用c3p0數(shù)據(jù)庫連接池的方法
本篇文章主要介紹了springboot 使用c3p0數(shù)據(jù)庫連接池的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
java根據(jù)當(dāng)前時間獲取yyyy-MM-dd?HH:mm:ss標(biāo)準(zhǔn)格式的時間代碼示例
在Java中可以使用java.time包中的LocalDateTime類和DateTimeFormatter類來獲取并格式化當(dāng)前時間為yyyy-MM-dd?HH:mm:ss的格式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
簡單講解Java設(shè)計模式編程中的單一職責(zé)原則
這篇文章主要介紹了Java設(shè)計模式編程中的單一職責(zé)原則,這在團(tuán)隊開發(fā)編寫接口時經(jīng)常使用這樣的約定,需要的朋友可以參考下2016-02-02
@Transaction,@Async在同一個類中注解失效的原因分析及解決
這篇文章主要介紹了@Transaction,@Async在同一個類中注解失效的原因分析及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

