文章目錄
一、ConcurrentHashMap緣起
- HashMap存在線程安全
話說在hashMap出現之際,就獲得了無數java開發者的青睞,但是隨着數據量的增多,迫使去解決多線程併發訪問,首先在java7中由於採用的擴容方式(參考:https://blog.csdn.net/qq_32679835/article/details/90447248#_330)
會產生死鎖的局面,即使在Java8中改進了原來頭插擴容方式,依舊會存在缺少鍵值對的情況,必須去找到一個替代者去解決HashMap線程不安全的處境,HashTable作爲老牌勢力,雖然滿足了線程安全的要求,但是他是通過synchronized關鍵字實現,通過互斥鎖效率問題就值得商榷。
- Collection.synchronizedMap解決問題
跟Hashtable可以說是難兄難弟,只是將Map的相應功能添加同步(synchrinized字段實現),性能與吞吐量存在問題,需要去選擇一種新型數據結構來達到線程安全,此時ConcurrentHashMap出現了。
二、ConcurrentHashMap 的升級打怪
—jdk1.7、jdk1.8實現方式存在差異
- JDK 1.7
1、HashMap還沒有加入紅黑樹的概念,ConcurrentHashMap 選擇通過segment分段鎖來實現併發,每一個segment會負責管理HashMap數組中的幾個桶,這樣每一次對數組的操作就不需要鎖定整個數組,而是只需要獲得當前segment鎖,相當於將鎖的粒度細化。
此時考慮size()這些具有全局功能的函數,你要獲取總共插入的元素個數,必然要獲取所有segment鎖,之後獲取總數,顯得有點麻煩,因此在此之前首先會先試圖使用無鎖的方式統計總數,這個嘗試會進行3次,如果在相鄰的2次計算中獲得的Segment的modCount次數一致,代表這兩次計算過程中都沒有發生過修改操作,那麼就可以當做最終結果返回。
2、ReentrantLock+segment+HashEntry實現
- jdk 1.8
1、加入了紅黑樹的概念,將原本在鏈表中查找數據的時間複雜度O(n)修改爲O(log n)
2、修改7中擴容的頭插方式,能夠很好的避免了死鎖問題
3、雖然在1.8中依舊保存segment的靜態類,完全摒棄了了segment分段鎖的概念,而是選擇了一種更加細粒度的操作,在每一個數組的node節點通過synchronized來同步
4、synchronized+cas+HashEntry實現
三、ConcurrentHashMap武器解析(底層實現)
(一) 常量
// node數組最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認初始值,必須是2的幕數
private static final int DEFAULT_CAPACITY = 16;
//數組可能最大值,需要與toArray()相關方法關聯
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//併發級別,遺留下來的,爲兼容以前的版本
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 負載因子
private static final float LOAD_FACTOR = 0.75f;
// 鏈表轉紅黑樹閥值,> 8 鏈表轉換爲紅黑樹
static final int TREEIFY_THRESHOLD = 8;
//樹轉鏈表閥值,小於等於6(tranfer時,lc、hc=0兩個計數器分別++記錄原bin、新binTreeNode數量,<=UNTREEIFY_THRESHOLD 則untreeify(lo))
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;
// 2^15-1,help resize的最大線程數
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 32-16=16,sizeCtl中記錄size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// forwarding nodes的hash值
static final int MOVED = -1;
// 樹根節點的hash值
static final int TREEBIN = -2;
// ReservationNode的hash值
static final int RESERVED = -3;
// 可用處理器數量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放node的數組
transient volatile Node<K,V>[] table;
//控制標識符,用來控制table的初始化和擴容的操作,不同的值有不同的含義
//當爲負數時:-1代表正在初始化,-N代表有N-1個線程正在 進行擴容
//當爲0時:代表當時的table還沒有被初始化
//當爲正數時:表示初始化或者下一次進行擴容的大小
private transient volatile int sizeCtl;
(二) 構造函數
ConcurrentHashMap()
以默認常量初始化ConcurrentHashMap(int initialCapacity)
以給定容量初始化,初始化容量爲大於initialCapacity最小二次冪(tableSizeFor()產生)ConcurrentHashMap(Map<? extends K, ? extends V> m)
容量爲傳入的Map大小,迭代(entrySet()函數)後通過putVal()傳參ConcurrentHashMap(int initialCapacity, float loadFactor)
默認調用底下參數ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
構造size = (long)(1.0 + (long)initialCapacity / loadFactor)
大小的Map
(三) 主要的內部靜態類
- Node類
1、屬性:hash(每一個元素的標識符,在1.7中用來定位segment的位置和數組位置,1.8中用來定位數組位置)、key、value、next
2、
1、需要提到的是find()方法,對map.get()提供虛擬支持,在Node類的子類中需要重寫find()方法
2、不允許直接設置value值,會拋出異常
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;
}
- TreeNode類:構成二叉樹的基本元素,重寫了find()方法
- TreeBin類:紅黑樹的頭部節點,選擇了cas+讀寫鎖的策略,能夠使得在writer之前需要需要等待reader完成,使用TreeBin代替了TreeNode節點,實際上在table數組中,存放的是TreeBin對象
- ForwardingNode類:數組擴容時候數據轉移的臨時節點,一個用於連接兩個table的節點類。它包含一個nextTable指針,用於指向下一張表。
- CounterCell 主要用來計數,首先整體的計數肯定通過cas操作baseCount變量,但是由於多線程操作,肯定會出現自選失敗的情況,如果出現自旋失敗,就需要去存儲到對應的COunterCell數組中,在獲取ConcurrenthashMap總個數的時候,只需要baseCount+CounterCell中的數量
(三) 關鍵方法
- Native方法:保證了在多線程下的可見性和有序性,遵循內存屏障
//在tab中查找偏移量爲i的Node節點
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i)
//通過cas設置偏移量爲i的值,如果等於期待值就設置變量
static final <K,V> Boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v)
//通過cas設置偏移量爲i的值
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)
- initTable():初始化數組
如果自旋設置sizeCtl =-1,表明當前線程正在初始化,初始化完成之後,sizeCtl 等於總容量的3/4,作爲觸發擴容的閾值
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); //有其他線程在初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//自旋,如果能夠成功,則進入初始化
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//默認容量爲16
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);//容量爲總容量的3/4
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
- get()方法:獲取key值對應的value
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//計算hash,通過高16位與低16位異或進行擾亂,避免hash衝突的發生
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
//鏈表頭結點
return e.val;
}
else if (eh < 0)
//特殊節點,通過find方法查找
/**
static final int MOVED = -1; // hash for forwarding nodes(擴容時候使用)
static final int TREEBIN = -2; // hash for roots of trees(轉化爲二叉樹使用)
static final int RESERVED = -3; // hash for transient reservations
**/
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;
}
-
put()方法:與HashMap方法相同,調用putval添加值
只需要理解每一個函數出現的作用,因爲COncurrentHashMap涉及到擴容,因此在之後擴容部分,將詳細講解helpTransfer()、addCount()
整個添加元素的過程
(1)首先獲取元素hash值,如果數組沒有初始化,需要去初始化數組,之後去添加元素
(2)空節點中添加了forward節點(虛擬表示:MOVED),說明有其他線程在擴容需要去協助擴容
(3)如果是插入到鏈表中(fh>0),插入首節點直接插入,插入節點key值相同,則覆蓋,否則遍歷鏈表,插入鏈表結尾
(4)如果是紅黑樹節點,插入到紅黑樹
(5)判斷鏈表節點加入後是否超過閾值8,超過閾值轉化爲紅黑樹
(6)計數,在ConcurrentHashMap中元素個數
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//如果爲null或者table數組未初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//添加到鏈表頭結點
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
}
//有一個線程在擴容,需要當前線程協助擴容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//鎖定當前節點,添加元素
synchronized (f) {
if (tabAt(tab, i) == f) {
//表明是非樹節點
if (fh >= 0) {
binCount = 1;
//遍歷鏈表所有節點
for (Node<K,V> e = f;; ++binCount) {
K ek;
//key值一致,則節點覆蓋
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
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;
}
}
}
//添加樹節點
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;
}
}
}
}
//鏈表節點數目大於8,轉化爲紅黑樹
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//將當前ConcurrentHashMap的元素數量+1
addCount(1L, binCount);
return null;
}
- 擴容函數
1、 addCount()計數函數涉及到的擴容部分(爲了理解transfer)
private final void addCount(long x, int check) {
//CounterCell實現單個線程的計數.....(省略了計數,後續補充)
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
//觸發擴容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//擴容標識符
int rs = resizeStamp(n);
//sc<0,其他線程在擴容
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//當前已經有其他線程在擴容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//唯一或者第一個擴容的線程
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
首先是resizeStamp(),該函數返回一個用於數據校驗的標誌位,意思是對長度爲n的table進行擴容。它將n的前導零(最高有效位之前的零的數量)和1 << 15做或運算,這時低16位的最高位爲1,其他都爲n的前導零。
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
rs << RESIZE_STAMP_SHIFT) + 2
含義,首先RESIZE_STAMP_SHIFT = 32-RESIZE_STAMP_BITS(默認爲16),rs是擴容的標識符,將該標識符左移16位,剩餘的16位就表示擴容線程的數量,由於-1表示table數組初始化,因此會選擇+2的方式,特別是該操作需要理解,在後邊會使用。
2、transfer()
單線程擴容
它的大體思想就是遍歷、複製的過程。首先根據運算得到需要遍歷的次數i,然後利用tabAt方法獲得i位置的元素:
(1)如果這個位置爲空,就在原table中的i位置放入forwardNode節點,這個也是觸發併發擴容的關鍵點;
(2)如果這個位置是Node節點(fh>=0),根據hash&n 判斷高位,將該鏈表拆分爲原位置鏈表和i+n(原始數組容量)的鏈表,放在對應位置
(3)如果這個位置是TreeBin節點(fh<0),根據hash&n 判斷高位,將該紅黑樹拆分爲原位置紅黑樹和i+n(原始數組容量)的紅黑樹,放在對應位置
(4)遍歷過所有的節點以後就完成了複製工作,這時讓nextTable作爲新的table,並且更新sizeCtl爲新容量的0.75倍,完成擴容。
多線程擴容
如果遍歷到的節點是forward節點,就向後繼續遍歷,再加上給節點上鎖的機制,就完成了多線程的控制。多線程遍歷節點,處理了一個節點,就把對應點的值set爲forward,另一個線程看到forward,就向後遍歷。這樣交叉就完成了複製工作。而且還很好的解決了線程安全的問題。
//一個過渡的table表 只有在擴容的時候纔會使用
private transient volatile Node<K,V>[] nextTable;
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//確定好每一個線程所負責的桶範圍
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
if (nextTab == null) { // 初始化桶,爲原來容量的2倍
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
//設置forwardingNode節點,用於標誌位
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//在擴容是忽略,有這個標識的bucket標識已經有其他線程處理(將對應桶中的數據放置到新數組中)
boolean advance = true;//併發擴容的關鍵屬性 如果等於true 說明這個節點已經處理過
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)//當前線程已經分配了bucket區域,但是依舊存在未處理的桶
advance = false;
else if ((nextIndex = transferIndex) <= 0) {//所有bucket已經分配完畢
i = -1;
advance = false;
}
//爲當前線程設置負責的bucket範圍
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//所有節點完成複製,則清空nextTable並將新數組賦值給原始數組
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//擴容線程數量-1,有很多線程,高16位標識擴容,低16位標識擴容線程數量
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//當前還有其他線程正在擴容,直接返回,如果==,就表示當前只有本線程擴容(參考addCout理解)
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
//空節點直接設置爲fwd節點
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // 其他線程已經處理當前bucket
else {
//鎖定當前節點
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
//與HashMap擴容原理相同,產生原鏈表和反序鏈表
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 {
//複製到i+原數組容量的位置
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);//添加到i+n的位置
setTabAt(tab, i, fwd);
advance = true;//該bucket已經處理
}
//樹節點擴容同樣思路,產生原始位置的紅黑樹與i+n位置的紅黑樹
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;
}
}
//轉換後不滿足閾值6,將二叉樹轉化爲鏈表
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;
}
}
}
}
}
}
3、tryPresize()擴容函數應用:再理解一下多線程擴容
private final void tryPresize(int size) {
//初始化容量
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
// sizeCtl是默認值或正整數
// 代表table還未初始化
// 或還沒有其他線程正在進行擴容
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
//初始化
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;//容量
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
//與addCount異曲同工,開始擴容
else if (tab == table) {
int rs = resizeStamp(n);
if (sc < 0) {
Node<K,V>[] nt;
//標識位不相等,擴容結束
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//增加一個線程擴容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//唯一或者第一個線程擴容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
- 計數addCount():補充前邊省略的部分
(1)更新baseCount值
(2)檢查是否需要擴容,並擴容(前邊已經說明)
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// cas更新baseCount失敗,每個線程都會通過ThreadLocalRandom.getProbe() & m尋址找到屬於它的CounterCell,然後進行計數。
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))) {
//調用fullAddCount(),該函數負責初始化CounterCells和更新計數
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
//統計總數(baseCount+所有CounterCells數組個數)
s = sumCount();
}
//.....擴容省略
}
(3)fullAddCount()初始化CounterCells和更新計數
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); //相當於找到每一個線程對應的Hash
h = ThreadLocalRandom.getProbe();
wasUncontended = true;//未競爭標識
}
boolean collide = false; // 衝突標識,True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;+
if ((as = counterCells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
//0和1,當它被設置爲0時表示沒有加鎖,當它被設置爲1時表示已被其他線程加鎖
if (cellsBusy == 0) { // Try to attach new Cell
//通過內部類,添加數量
CounterCell r = new CounterCell(x);
//自旋加鎖
if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
//添加計數變量
try { // Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;//釋放鎖
}
// 如果未成功
// 代表as[(n - 1) & h]這個位置的Cell已經被其他線程設置
// 那麼就從循環頭重新開始
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // (a = as[(n - 1) & h]) != null 表示有其他線程競爭
wasUncontended = true; // 需要去重新計算hash
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))//成功則退出循環
break;
else if (counterCells != as || n >= NCPU) // 達到了數組容量最大值,無法擴容
collide = false;
else if (!collide)
collide = true;
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {//擴容爲原來的2倍,並數據遷移到新數組
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = ThreadLocalRandom.advanceProbe(h);//當前線程重新計算hash
}
//表明數組未初始化
else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try { // 初始化counterCells 數組
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2];//初始化容量爲2
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
// 初始化CounterCell數組成功,退出循環
if (init)
break;
}
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; //失敗則使用baseCount
}
}
四、引用
【1】 jdk1.7 segment結構圖:https://sylvanassun.github.io/2018/03/16/2018-03-16-map_family/#more
【2】參考主力:https://blog.csdn.net/yjp198713/article/details/79004798#forwardingnode
【3】參考主力:https://blog.csdn.net/qq_30336433/article/details/82586416#擴容
五、結語
看Concurrenthashmap比較吃力,主要是分析了1.8中的特性,對於1.7中的特性只是提到了一小塊,其中還有很多部分需要日後繼續補充。