java8 ConcurrentHashMap源碼學習2
上文: java8 ConcurrentHashMap源碼學習.
ConcurrentHashMap
之前發現常用的remove方法還有helptransfer並沒有整理出來, 這裏把學習心得整理一下, 順便把get也貼上來
remove
其實這裏跟put一樣也是直接引用另一個方法
public V remove(Object key) {
return replaceNode(key, null, null);
}
這裏看到用了replaceNode方法入參是key, null, null
下面的代碼裏可以看見這三個參數的名稱
key就是map中的key, value就是要替換的值, cv就是要替換的key的value
如果value爲空, 那麼那麼就是替換key的value而不是做刪除
如果cv爲空那麼就不判斷oldValue直接進入替換或者刪除操作, 否則判斷cv是否與oldValue相等
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;
// MOVED代表當前有遷移工作, 加入遷移
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 一個標誌位, 標誌是否有進行替換或者刪除工作
boolean validated = false;
synchronized (f) {
// 確保上鎖前後數據一致
if (tabAt(tab, i) == f) {
// 置於這裏爲什麼fh>=0纔是鏈表, 因爲在構造紅黑樹的時候
// 構造函數會將hash變量設置爲TREEBIN, 而TREEBIN的值是-2
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
// 找到這個key
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
// 上文提到的cv判斷
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
// 上文提到的value判斷替換或者刪除
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;
// 判斷紅黑樹是否存在這個key
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;
// 這裏removeTreeNode返回的bool不是代表刪除的成功與否
// 這裏返回的是是否需要將紅黑樹轉換爲鏈表
// 下文會寫到這個方法
else if (t.removeTreeNode(p))
// 用untreeify方法將紅黑樹鏈表化
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
// 已經操作成功
if (validated) {
if (oldVal != null) {
// 這裏表示這是個刪除操作, 將table中的元素計數器-1
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
removeTreeNode
主要講一下返回的true和false, 紅黑樹操作按下不表, (因爲👴還不太清楚, 下次一定學
final boolean removeTreeNode(TreeNode<K,V> p) {
TreeNode<K,V> next = (TreeNode<K,V>)p.next;
TreeNode<K,V> pred = p.prev; // unlink traversal pointers
TreeNode<K,V> r, rl;
// 在TreeBin裏面是有first屬性的, 因爲在treeifyBin這個方法中
// 先構成雙鏈表, 然後傳入TreeBin的構造器中構造紅黑樹
if (pred == null)
// 這裏pred=null代表着要刪除的p是第一個結點, first後移
first = next;
else
// 否則pred指向p的下一個
pred.next = next;
if (next != null)
// 雙鏈表操作
next.prev = pred;
// 特判first爲空的情況, 直接返回true表示將紅黑樹鏈表化, 出來的當然也是null
if (first == null) {
root = null;
return true;
}
// 這裏是判斷紅黑樹大小, 沒有采用結點個數感覺挺奇怪的
// 因爲就算紅黑樹中有十個結點也可以構成true的情況, 我下面貼個圖
if ((r = root) == null || r.right == null || // too small
(rl = r.left) == null || rl.left == null)
return true;
// 給紅黑樹上鎖進行刪除操作, 這下面涉及的就是紅黑樹的刪除操作, 雙鏈表操作上面做完了
lockRoot();
try {
TreeNode<K,V> replacement;
TreeNode<K,V> pl = p.left;
TreeNode<K,V> pr = p.right;
if (pl != null && pr != null) {
TreeNode<K,V> s = pr, sl;
while ((sl = s.left) != null) // find successor
s = sl;
boolean c = s.red; s.red = p.red; p.red = c; // swap colors
TreeNode<K,V> sr = s.right;
TreeNode<K,V> pp = p.parent;
if (s == pr) { // p was s's direct parent
p.parent = s;
s.right = p;
}
else {
TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
if ((s.left = pl) != null)
pl.parent = s;
if ((s.parent = pp) == null)
r = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
replacement = p;
if (replacement != p) {
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
r = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null;
}
root = (p.red) ? r : balanceDeletion(r, replacement);
if (p == replacement) { // detach pointers
TreeNode<K,V> pp;
if ((pp = p.parent) != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
p.parent = null;
}
}
} finally {
// 解鎖
unlockRoot();
}
// 做紅黑樹的結構檢查
assert checkInvariants(root);
return false;
}
圖貼上面了, 如果這一步理解有誤, 希望評論區可以指出
helpTransfer
算是最短的方法了吧??? 因爲邏輯跟transfer裏面差不多, 所以上次就沒去搭理
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// 判斷當前tab不爲空, 並且傳入結點正在遷移, 獲得擴容好的nextTable
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 根據tab.length算出一個高十六位負數
int rs = resizeStamp(tab.length);
// 循環判斷獲得的數據是否不變, sizeCtl<0則說明正在遷移
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// 遷移工作已經完成, 退出循環
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 線程參與遷移, cas對sc+1表示參與遷移的線程數+1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
get
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
// cas獲取當前的key所在的桶, 保證這個桶的數據是最新的
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)
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;
}
之前find點進去還是鏈表的操作, 嚇了一跳, 結果發現不是在TreeNode裏面的, 下面纔是真正使用的find,
堂堂正正的紅黑樹搜索
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
後記
沒有後記了, 如果還有下一篇再寫這篇後記