一,ConcurrentHashMap概述
1,ConcurrentHashMap
* ConcurrentHashMap 和 HashMap 原理基本一致,就是在 HashMap 的基礎上增加了鎖處理,支持併發操作,在實現上比 HashMap 更復雜點。先比較與 JDK7,JDK8在實現上,修改原來通過 Segment 進行加鎖的方式改爲通過 Node 進行加鎖,同時在鏈表方面,如果鏈表長度超過閾值,則轉換爲紅黑樹;紅黑樹部分沒有分析,因爲不會。。。
2,類圖
3,常量及常用API
3.1,常量
// 最大允許長度
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認長度
private static final int DEFAULT_CAPACITY = 16;
// 最大數組長度-轉數組使用
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 擴容因子
private static final float LOAD_FACTOR = 0.75f;
// 鏈表 -> 紅黑樹轉換閾值
static final int TREEIFY_THRESHOLD = 8;
// 紅黑水 -> 鏈表轉換閾值
static final int UNTREEIFY_THRESHOLD = 6;
// 最小樹化容量,如果需要轉換紅黑樹並且容量小於該值,則先擴容
static final int MIN_TREEIFY_CAPACITY = 64;
// 擴容操作中,單個線程的最小步進
// 數據遷移通過分段遷移,由多線程協調執行,最小段數量爲16,則如果長度爲16,由一個線程進行擴容
private static final int MIN_TRANSFER_STRIDE = 16;
// 擴容操作使用
private static int RESIZE_STAMP_BITS = 16;
// 擴容操作使用,進行sizeCtl高低位移動,進行擴容線程數判斷
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//最大擴容線程數量
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// ForwardingNode的hash值,ForwardingNode是一種臨時節點,在擴進行中才會出現,並且它不存儲實際的數據,ForwardingNode繼承自Node,默認hash初始化爲-1
static final int MOVED = -1;
// 紅黑樹的HASH值
static final int TREEBIN = -2;
// ReservationNode的hash值,ReservationNode是一個保留節點,就是個佔位符
static final int RESERVED = -3;
// 用於和負數hash值進行 & 運算,將其轉化爲正數(絕對值不相等)
static final int HASH_BITS = 0x7fffffff;
// CPU的核心數
static final int NCPU = Runtime.getRuntime().availableProcessors();
3.2,變量
// 鏈表
transient volatile Node<K,V>[] table;
// 擴容時候的新鏈表
private transient volatile Node<K,V>[] nextTable;
// 分段計數,記錄
private transient volatile CounterCell[] counterCells;
/*
* 非常重要的一個屬性,源碼中的英文翻譯,直譯過來是下面的四行文字的意思
* sizeCtl = -1,表示有線程正在進行真正的初始化操作
* sizeCtl = -(1 + nThreads),表示有nThreads個線程正在進行擴容操作
* sizeCtl > 0,表示接下來的真正的初始化操作中使用的容量,或者初始化/擴容完成後的閾值
* sizeCtl = 0,默認值,此時在真正的初始化操作中使用默認容量
*/
private transient volatile int sizeCtl;
// 擴容任務的起始下標
private transient volatile int transferIndex;
// CAS自旋鎖標誌位,用於初始化,或者counterCells擴容時
private transient volatile int cellsBusy;
// 計數器基本值,主要在沒有碰到多線程競爭時使用,需要通過CAS進行更新
private transient volatile long baseCount;
/* Contended類 */
// 當前統計數量
volatile long value;
3.3,常用API
/* 初始化部分 */
public ConcurrentHashMap();
public ConcurrentHashMap(int initialCapacity);
public ConcurrentHashMap(Map<? extends K, ? extends V> m);
/* 寫數據部分 */
public V put(K key, V value);
public void putAll(Map<? extends K, ? extends V> m);
/* 讀數據部分 */
public V get(Object key);
/* 移除數據部分 */
public V remove(Object key);
/* 獲取長度 */
public int size();
3.4,核心原子方法
// 獲取 i 索引處對象
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// 把 i 索引處爲 c 的對象替換爲 v
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 把 i 索引處對象設置爲 v
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
四,源碼分析
1,初始化源碼分析
1.1,ConcurrentHashMap(int initialCapacity)
* ConcurrentHashMap(int initialCapacity)
public ConcurrentHashMap(int initialCapacity) {
// 校驗參數合法性
if (initialCapacity < 0)
throw new IllegalArgumentException();
// 大於最大值,取最大值
// 合法數據,進行數據重處理,向上取整爲2的整數次方
// 向上取整數倍爲了再下標計算時更分散,該部分後續有機會在 HashMap 中分析
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
* tableSizeFor(int c)
private static final int tableSizeFor(int c) {
// 通過下列一系列或等於操作後,獲取到的值肯定是2的整數次方
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
1.2,ConcurrentHashMap(Map<? extends K, ? extends V> m)
* ConcurrentHashMap(Map<? extends K, ? extends V> m)
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
// 初始化長度爲默認長度,即16
this.sizeCtl = DEFAULT_CAPACITY;
// 調用 putAll 方法進行數據添加
// putAll 內部循環調用 putVal 方法,後續分析
putAll(m);
}
2,put()源碼分析
2.1,put() 總述
* put(K key, V value)
public V put(K key, V value) {
// 直接調用 putVal() 進行數據添加
return putVal(key, value, false);
}
* putVal(K key, V value, boolean onlyIfAbsent)
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 獲取hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 第一階段:初始化階段
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;
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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 第三階段:數量統計階段
addCount(1L, binCount);
return null;
}
* spread(int h):獲取key值對應的hash值,此處注意不是下標
static final int spread(int h) {
// 此處用當前key哈希值的高16位與低16位按位異或之後,在於魔數進行與運算
// 步驟的目的是儘量使hash值分散,之後與(length - 1)進行與運算,儘量保證數據能均勻分散
// 這也是爲什麼長度要是2的整數次方,比如16的二進制是100000,減1就是11111,再進行與運算,儘量保證分散
return (h ^ (h >>> 16)) & HASH_BITS;
}
static final int HASH_BITS = 0x7fffffff;
2.2,put() 第一階段:初始化階段
* initTable()
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 此處通過自旋,保證初始化成功
while ((tab = table) == null || tab.length == 0) {
// sizeCtl 在初始化時候已經複製爲長度,即tab.length
// sizeCtl 表示正在初始化或者正在擴容
if ((sc = sizeCtl) < 0)
Thread.yield();
// 初始化時候,將 SIZECTL 的值修改爲 -1,該狀態比較重要,後續多處會涉及
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 再對 table 進行判斷,確定爲空時候進行初始化
if ((tab = table) == null || tab.length == 0) {
// 長度爲0,取默認長度,即16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
// 初始化 Node 數組,並賦值給 table
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 此處的結果是 3/4 * n,即0.75 * n,也就是擴容因子
// 後續如果數量超過 sc 的數量,說明需要擴容
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
2.3,put() 第二階段(1):數據統計階段
* ConcurrentHashMap 數據統計採用分段統計。使用 CounterCell 數組存儲每一段的數據數量,再獲取總數據時,遍歷求和。
* addCount(long x, int check)
// x 表示增加數量
// check 表示鏈表數量
// 總數量增加後可能會觸發擴容,即正式觸發第三階段
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 首先對 CounterCell[] 進行判空,如果 CounterCell[] 爲空,則依舊嘗試通過CAS修改 baseCount 的值,由該值記錄元素個數
// 如果CAS失敗,說明存在線程競爭,則通過 CounterCell 記錄個數
// CounterCell[] 不爲空,直接通過 CounterCell 記錄個數
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
// 是否衝突,默認表示沒有衝突
boolean uncontended = true;
// 計數表爲空,直接調用下面方法
// 計數表元素爲空,直接調用下面方法; 技術表元素不爲空,則隨機獲取一個有效元素作爲 CounterCell 進行CAS處理
// ThreadLocalRandom.getProbe() 隨機數,和 m 即計數表長度進行與運算後,取一個有效的下標
// 通過CAS修改 CounterCell 的值,如果修改失敗,說明存在線程競爭,調用下面方法
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// if條件走完後,沒有遞增成功,則進行遞增操作
fullAddCount(x, uncontended);
return;
}
// 鏈表長度小於等於1,不考慮擴容
// 鏈表轉紅黑樹需要滿足兩個條件:鏈表長度大於8,數組長度大於64
// 如果鏈表長度大於8,但是數組長度沒有超過64,則先進行擴容操作
if (check <= 1)
return;
// 統計 ConcurrentHashMap 的元素個數
s = sumCount();
}
// >=0,表示該位置存在元素
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// s表示當前元素總數,sizeCtl表示擴容閾值,大於表示需要擴容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 此處在擴容階段再具體分析,涉及輔助擴容方面東西,此處正式開始第三階段
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;
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();
}
}
}
* fullAddCount(long x, boolean wasUncontended):增加數量,該部分不涉及擴容
// x : 增加數量
// wasUncontended:是否存在衝突
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 獲取線程的 probe 值,值如果沒有初始化,則進行初始化,並重置未衝突表示爲true
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false;
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
// CounterCell[] 數組不爲空,已經存在有效的 CounterCell 元素
if ((as = counterCells) != null && (n = as.length) > 0) {
// 獲取下標,找到對應的 CounterCell 進行遞增操作
if ((a = as[(n - 1) & h]) == null) {
// 爲0表示不存在線程進行遞增處理
if (cellsBusy == 0) {
// 初始化一個 CounterCell,並賦初值爲遞增值
CounterCell r = new CounterCell(x);
// 將 cellsBusy 狀態改爲正在進行遞增處理
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try {
// 將初始化的 CounterCell 添加到數組中
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;
}
// 創建失敗,說明存在競爭,則繼續自旋處理
if (created)
break;
continue;
}
}
collide = false;
}
// 後續操作首先說明 CounterCell 不爲空
// 貌似沒有應用,先不用管
else if (!wasUncontended)
wasUncontended = true;
// 通過CAS遞增 value 值,修改成功則執行完成
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// 此處說明競爭下衝突,獲取已經超過CPU數量,當前循環失敗
else if (counterCells != as || n >= NCPU)
collide = false;
else if (!collide)
collide = true;
// 對 CounterCell 數組進行擴容,保證高併發下的操作
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {
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;
}
h = ThreadLocalRandom.advanceProbe(h);
}
// cellsBusy 爲0,表示沒有線程在做初始化,修改值爲1
// CounterCell[] 數組爲空,但是不爲null,如果不爲空會走上一個if,如果爲null會走下一個if
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
// 此處添加初始化標誌
boolean init = false;
try {
// 第一個if已經進行了as複製,所以此處==,不存在競爭情況下基本成立
if (counterCells == as) {
// 初始化CounterCell[]數組,數組長度初始化爲2
CounterCell[] rs = new CounterCell[2];
// 將遞增的數量,複製給其中之一,
rs[h & 1] = new CounterCell(x);
counterCells = rs;
// 初始化爲true
init = true;
}
// 遞增完成後,重置值
} finally {
cellsBusy = 0;
}
if (init)
break;
}
// 上面兩條都不符合,則直接對baseCount遞增,CAS成功後返回
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break;
// 否則進入自旋
}
}
* sumCount():獲取 ConcurrentHashMap 元素總數量
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
// 如果不存在線程競爭,則 counterCells 爲空,則數據存儲在 baseCount 中
long sum = baseCount;
// counterCells 不爲空,存在線程競爭,則遍歷獲取總數
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
2.4,put() 第二階段(2):擴容階段
* 第三階段擴容擴容階段分爲兩部分內容,首先是 addCount() 部分的擴容操作,因爲數量遞增引起的擴容,算是正式開啓擴容階段;第二部分是 putVal() 時候的分支條件進行輔助擴容,是在已經有線程進行擴容的基礎上,如果還存在線程進行數據操作,則進行輔助擴容。擴容部分應該是難度最大的一部分
* addCount(long x, int check):再探擴容觸發條件
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
... // 中間部分省略,上面已經分析
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);
// sizeCtl小於0,說明有其他線程正在進行擴容處理,則此時sc已經經過擴容戳處理
if (sc < 0) {
// 這五個條件有一個爲true,說明當前線程不再進行擴容處理
// sc >>> RESIZE_STAMP_SHIFT != rs,如果不存在線程競爭,則此時獲取到的值應該相等,不等於說明存在競爭
// sc == rs + 1 : 擴容已經結束
// sc == rs + MAX_RESIZERS:幫助線程已經最大值
// (nt = nextTable) == null:擴容已經結束
// transferIndex <= 0:表示所有的擴容任務已經被領完,沒有剩餘的hash桶來給自己線程做擴容
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// 當前線程嘗試幫助擴容,如果成功,則進行擴容
// sc + 1:表示擴容線程數量遞增
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// sizeCtl > 0,直接進行擴容處理
// rs << RESIZE_STAMP_SHIFT:將sc設置爲一個負數,+2表示有一個線程在執行擴容任務
// 計算完resizeStamp()再繼續看,結果集爲 00000000 00000000 10000000 00011011
// 則sc替換後的值應該爲 10000000 00011011 00000000 00000010,此時低16位就表示並行線程數,後續表示並行線程數也是對該值加1
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
* resizeStamp(int n):擴容戳詳細分析
// n:表長度
static final int resizeStamp(int n) {
// Integer.numberOfLeadingZeros 這個方法是返回無符號整數 n 最高位非 0 位前面的 0 的個數
// 比如 16 的32位是 00000000 00000000 00000000 00010000,則值爲27
// RESIZE_STAMP_BITS爲16,就是1右移15位,及 00000000 00000000 10000000 00000000
// 最終結果就是 00000000 00000000 10000000 00011011
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
* transfer(Node<K,V>[] tab, Node<K,V>[] nextTab):擴容,擴容是核心內容,重點在於數據遷移及鏈表部分處理。ConcurrentHashMap 內容通過多線程來進行協調擴容,把 Node 數組作爲一個多線程共享的共享隊列,然後通過指針來劃分每個線程負責的區間,每個線程區間通過線程逆向遍歷來進行擴容。
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 一個CPU一次最少默認處理16個hash位置,即步進
// 大於則計算,小於則取默認16,由一個CPU執行
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
// nextTab 表示需要擴容的表,如果爲空,則初始化,注意初始化長度爲當前長度的二倍
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;
}
// 新數組長度
int nextn = nextTab.length;
// 構建一個 ForwardingNode 節點,表示一個正在遷移的Node,hash值爲-1(MOVED),表示構建一個標示位
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// 下標推進標識,爲true,說明需要推進,爲false,說明未處理完成
// 推進表示推進一個 stride 的區間
boolean advance = true;
// 是否已經完成,默認false,即對當前 stride 區間數據遷移完成
boolean finishing = false;
// i表示當前處理的槽位序號,bound表示需要處理的槽位邊界
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 該循環通過CAS不斷嘗試爲當前線程分配任務,即一個 stride
// 線程對 stride 區間處理,從尾部到頭部倒敘處理
// 如果當前線程已經被分配過 stride 區域,那麼通過--i遞減後繼續進行節點遷移
while (advance) {
int nextIndex, nextBound;
// bound 表示 stride 邊界的頭節點
// i 表示 stride 邊界的尾結點
// 因爲是倒敘進行處理,所以在任務處理中, i 肯定大於 bound
if (--i >= bound || finishing)
advance = false;
// transferIndex 表示擴容前數組長度,賦值給 nextIndex
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// 上一個if分支已經給 nextIndex 賦值
// 假如數組長度目前爲16,則 nextIndex爲16,nextBound爲0,當前任務區間爲 [0, 15]
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// i < 0 說明已經遍歷完成,也就是當前線程已經處理完成所有的 stride
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 總任務已經完成
if (finishing) {
// 對相關參數進行重置後退出
nextTable = null;
table = nextTab;
// 更新擴容閾值
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 此處表示總任務沒有完成,只是當前線程已經處理完成任務
// 之前有提過多線程同時處理時對 sizeCtl 進行高低位操作後用來計數,執行完成後,對應計數遞減
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 在擴容時,會對 SIZECTL 進行基本計算賦值 (rs << RESIZE_STAMP_SHIFT) + 2)
// 所以在對最後一個線程進行處理時,必然存在條件成立
// 如果條件不成立,說明已經全部執行完成,直接退出
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 任務執行完成,擴容結束沒更新finishing變量
finishing = advance = true;
// 再次循環檢查一次
i = n;
}
}
// 如果i位置元素空,則放入剛纔初始化的 ForwardingNode 空節點,標識該位置正在遷移
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// 表示位置已經完成了遷移
else if ((fh = f.hash) == MOVED)
advance = true;
else {
// 對當前遍歷的hash位置進行遷移,加鎖處理,這也是JDK8不同於JDK7的一個主要地方,對Node加鎖
// 再分析這部分前先對鏈表遷移的高低鏈原理進行分析
synchronized (f) {
...
}
}
}
}
* 鏈表遷移的高低鏈原理分析
// 首先明確一個概念,Map的下標計算是通過 hash & length 獲取到的下標,而不是單純的 hash 表示
// 所以在每一個鏈表表示的節點上,雖然衆多元素下標值衝突,掛載同一個下標點下,但是 hash 值基本上是不一致的
// 在這種情況下,鏈表遷移的hash鏈表示,其實是對 hash 值二進制下,與原數組長度進行與操作後進行非0判斷
// 該位置數字如果爲0,則表示低鏈,該位置數組如果爲1,則表示高鏈
// 低鏈說明該鏈上的元素在當前索引不變
// 高鏈表示該鏈上的元素會隨着擴容同時增加一個擴容長度的下標,比如擴容前長度爲16,下標爲4,則擴容後下標爲20
// 計算方式如下,比如對於hash值4和20的元素,在16原始長度下
4 = 00000000 00000000 00000000 00000100
15 = 00000000 00000000 00000000 00001111 &
---------------------------------------------
00000000 00000000 00000000 00000100 = 4 (下標爲4)
20 = 00000000 00000000 00000000 00010100
15 = 00000000 00000000 00000000 00001111 &
---------------------------------------------
00000000 00000000 00000000 00000100 = 4 (下標爲4)
// 如上,在16長度下,對於4和20,下標都爲4,此時如果對16進行擴容,擴容到32
4 = 00000000 00000000 00000000 00000100
16 = 00000000 00000000 00000000 00010000 &
---------------------------------------------
00000000 00000000 00000000 00000100 = 4 (下標爲4)
20 = 00000000 00000000 00000000 00010100
31 = 00000000 00000000 00000000 00010000 &
---------------------------------------------
00000000 00000000 00000000 00010100 = 20 (下標爲20 = 4 + 16)
// 這個例子就是說明,數組庫容後,(新數組長度 - 1)最高非0位爲倒數第五位,
// 則對4和20的第五位進行判斷,4爲0,則在低鏈。20爲1,則在高鏈
// 數據遷移時,低鏈位置不動,高鏈位置 = 原位置 + 擴容長度
// 繼續往上加衝突索引,到36,52,68...也是遵循這個規則
* 繼續分析槽點元素遷移
// 對當前遍歷的hash位置進行遷移,加鎖處理,這也是JDK8不同於JDK7的一個主要地方,對Node加鎖
// 再分析這部分前先對鏈表遷移的高低鏈原理進行分析
synchronized (f) {
// 判斷當前必然有值
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
// hash 值大於0,說明當前節點表示單元素或者鏈表
if (fh >= 0) {
// n 爲原數組長度,下標位置計算,是 fh & (n - 1)
// 此處直接 & n,爲了獲取元素 hash 值對應長度1位置的數字是否爲0
int runBit = fh & n;
Node<K,V> lastRun = f;
// 因爲 Node 爲單向鏈表,此處直接遍歷到尾部,獲取到尾部節點
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
// 先把尾部節點進行高低鏈歸屬處理
// 爲0表示在低鏈
// 爲1表示在高鏈
if (runBit == 0) {
ln = lastRun; // low
hn = null;
}
else {
hn = lastRun; // high
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);
// 把舊錶中的hash桶中放置標識位節點,表示已經被處理
setTabAt(tab, i, fwd);
advance = true;
}
// 表示紅黑樹,紅黑樹部分真TM不會,以後會了再補充吧 TODO
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;
}
}
}
2.5,put() 第三階段:輔助擴容階段
* helpTransfer(Node<K,V>[] tab, Node<K,V> f)
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// f instanceof ForwardingNode 表示當前節點正在擴容,則輔助擴容
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 生成擴容戳
int rs = resizeStamp(tab.length);
// 說明擴容還爲完成,參與擴容
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// (sc >>> RESIZE_STAMP_SHIFT) != rs:說明擴容已經結束了
// sc == rs + 1:說明擴容已經結束 這兩個條件根據擴容戳的高低位轉換後判斷可以得出
// sc == rs + MAX_RESIZERS:已經到達最大擴容數,不再參與
// transferIndex <= 0:所有hash位置已經分配完畢
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 參與擴容,擴容線程數+1,進行庫容處理
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
2.6,put() 第四階段:鏈表到紅黑樹構造階段
* 繼續回到 putVal() 方法中,單掕出來查看鏈表到紅黑樹構造的代碼,紅黑樹不分析,因爲不會
else {
V oldVal = null;
// 對Node進行加鎖,同步處理
synchronized (f) {
// 再次判斷下標位置是否是f節點
if (tabAt(tab, i) == f) {
// 頭節點值大於0,說明是鏈表
if (fh >= 0) {
// 記錄鏈表長度
binCount = 1;
// 遍歷鏈表
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 判斷是否是相同hash,hash相同進行value替換
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;
}
}
}
// 頭節點置小於0,說明是紅黑樹
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;
}
}
* treeifyBin(Node<K,V>[] tab, int index):鏈表轉樹處理,此處多跟一步,爲了說明在鏈表長度大於閾值後,還要判斷數組長度是否大於閾值,不大於後,直接擴容
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
// 判斷數組長度是否大於閾值64,如果不大於,首先觸發擴容
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
// 條件全部滿足,觸發樹
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
3,get()源碼分析
* get(Object key)
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 獲取key對應的hash值
int h = spread(key.hashCode());
// 數組不爲空,且數組對應元素不爲空,繼續尋找
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// hash值匹配,直接返回值
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// hash值爲0,表示爲紅黑數,不分析。。。
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 走到此處說明爲鏈表。遍歷樹不對hash,獲取值
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
4,remove()源碼分析
* remove(Object key)
public V remove(Object key) {
// 移除節點,並返回當前節點,不存在返回null
return replaceNode(key, null, null);
}
* replaceNode(Object key, V value, Object cv)
final V replaceNode(Object key, V value, Object cv) {
// 獲取哈希值
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 數組爲空,或者下標所在元素不存在,直接返回
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
// 數組正在擴容中,輔助擴容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
// 節點元素不爲空,判斷移除
V oldVal = null;
boolean validated = false;
synchronized (f) {
// 二次校驗
if (tabAt(tab, i) == f) {
// 當前下標元素存在,爲單個元素或者鏈表
if (fh >= 0) {
validated = true;
// 遍歷當前節點的鏈表進行處理
for (Node<K,V> e = f, pred = null;;) {
K ek;
// 此部分表示找到對應的hash值
// 然後移除該節點,將後續節點填充
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
// 紅黑樹,不分析。。。
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
// 返回oldValue
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}