ConcurrentHashMap
put操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 本質上和hashmap沒有什麼差別,都是把hashcode進行對半異或,這樣就可以用一半的位數,集合了32位長度的信息
int hash = spread(key.hashCode());
int binCount = 0;
Node<K,V>[] tab = table;
// 這裏循環的目的是cas的重試操作
for (;;) {
// 指向待插入元素應當插入的位置
Node<K,V> f;
// 元素f對應的哈希值
int fh;
// 當前hash表數組的長度容量
int n;
int i;
// 如果哈希數組還未初始化,或者容量無效,則需要初始化一個哈希數組
if (tab == null || (n = tab.length) == 0) {
tab = initTable();
// 這裏n -1 & hash 是經典取餘操作,參考之前hashmap
} else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 如果當前位置是空的,那麼就可以通過cas設置對應的值
Node<K, V> newNode = new Node<K,V>(hash, key, value, null);
// 用一次 CAS 操作將這個新值放入其中即可,這個 put 操作差不多就結束了,可以拉到最後面了
// 如果 CAS 失敗,那就是有併發操作,進到下一個循環就好了
if (casTabAt(tab, i, null, newNode)) {
// 插入完成,跳出循環,如果更新失敗,重新循環進入
break; // no lock when adding to empty bin
}
} else if ((fh = f.hash) == MOVED) {
/*
* 如果待插入元素所在的哈希槽上已經有別的結點存在,且該結點類型爲MOVED
* 說明當前哈希數組正在擴容中,此時,可以嘗試加速擴容過程
*/
tab = helpTransfer(tab, f);
} else {
V oldVal = null;
// 這裏避免併發,對f節點的引用進行上鎖
synchronized (f) {
// 如果tab[i]==f,則代表當前待插入狀態仍然可信
if (tabAt(tab, i) == f) {
// fh > 0 標識不是在擴容,是正常節點
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) {
// 哈希槽(鏈)上的元素數量增加到TREEIFY_THRESHOLD後,這些元素進入波動期,即將從鏈表轉換爲紅黑樹
if (binCount >= TREEIFY_THRESHOLD) {
// 注意,這裏也只是鎖了這一個節點,注意,這裏不一定一定是轉換爲紅黑樹
// 如果整個tab長度是小於64的話,這裏會選擇自動擴容,如果已經超過64了,才考慮轉換紅黑樹
treeifyBin(tab, i);
}
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
get和remove操作
略,就是根據索引找節點
擴容
核心方法就是transfer
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length;
// stride 在單核下直接等於 n,多核模式下爲 (n>>>3)/NCPU,最小值是 16
// 將這 n 個任務分爲多個任務包,每個任務包有 stride 個任務
int stride = (NCPU > 1) ? (n >>> 3) / NCPU : n;
if (stride < MIN_TRANSFER_STRIDE) {
stride = MIN_TRANSFER_STRIDE; // subdivide range
}
if (nextTab == null) { // initiating
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 翻譯過來就是正在被遷移的 Node
// 這個構造方法會生成一個Node,key、value 和 next 都爲 null,關鍵是 hash 爲 MOVED
// 後面我們會看到,原數組中位置 i 處的節點完成遷移工作後,
// 就會將位置 i 處設置爲這個 ForwardingNode,用來告訴其他線程該位置已經處理過了
// 所以它其實相當於是一個標誌。, 這個在put的時候會判斷節點hash值,用來判斷是否需要協助擴容
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// advance 指的是做完了一個位置的遷移工作,可以準備做下一個位置的了
boolean advance = 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) {
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;
}
}
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; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
// 真正的開始數據遷移,先對f節點上鎖,f是tab中的一個位置
synchronized (f) {
// 保證數據正確性沒有發生變化
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
// 頭節點的 hash 大於 0,說明是鏈表的 Node 節點
if (fh >= 0) {
// 下面這一塊和 Java7 中的 ConcurrentHashMap 遷移是差不多的,
// 需要將鏈表一分爲二,
// 找到原鏈表中的 lastRun,然後 lastRun 及其之後的節點是一起進行遷移的
// lastRun 之前的節點需要進行克隆,然後分到兩個鏈表中
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;
// ph是這個節點的hash值&n如果爲0,說明再n-1部分,位置沒變,還是放入之前的位置
if ((ph & n) == 0) {
ln = new Node<K,V>(ph, pk, pv, ln);
} else {
// 不爲0,說明再n-1上面的位置,位置變了,那麼就重新設置位置
hn = new Node<K,V>(ph, pk, pv, hn);
}
}
// 其中的一個鏈表放在新數組的位置 i
setTabAt(nextTab, i, ln);
// 把head放到數組i+n的位置
setTabAt(nextTab, i + n, hn);
// 將原數組該位置處設置爲 fwd,代表該位置已經處理完畢,
// 其他線程一旦看到該位置的 hash 值爲 MOVED,就不會進行遷移了
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;
}
}
}
}
}
}