【JDK源碼分析系列】ConcurrentHashMap 源碼分析 -- 基本結構及其線程安全體現
【1】Node 基本節點/普通節點
// Node:基本節點/普通節點
// 在鏈表形式保存才使用這種節點,它存儲實際的數據
// val 和 next 被 volatile 修飾
// Map.Entry是個接口
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//當前node的hash值
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
//不支持來自ConcurrentHashMap外部的修改,迭代操作需要通過另外一個內部類MapEntry來代理,迭代寫會重新執行一次put操作
// 迭代中可以改變value,是一種寫操作,此時需要保證這個節點還在map中
// 因此就重新put一次:節點不存在了,可以重新讓它存在;節點還存在,相當於replace一次
// 設計成這樣主要是因爲ConcurrentHashMap並非爲了迭代操作而設計,它的迭代操作和其他寫操作不好併發
// 迭代時的讀寫都是弱一致性的,碰見併發修改時儘量維護迭代的一致性
// 返回值V也可能是個過時的值,保證V是最新的值會比較困難,而且得不償失
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
// 從此節點開始查找k對應的節點
// 這裏的實現是專爲鏈表實現的,一般作用於頭結點,各種特殊的子類有自己獨特的實現
// 不過主體代碼中進行鏈表查找時,因爲要特殊判斷下第一個節點,所以很少直接用這個方法
// 而是直接寫循環遍歷鏈表,子類的查找則是用子類中重寫的find方法
/**
* Virtualized support for map.get(); overridden in subclasses.
*/
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;
}
}
【2】TreeNode 紅黑樹節點
//紅黑樹的節點
//ConcurrentHashMap對此節點的操作,都會由TreeBin來代理執行
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links 紅黑樹父節點
TreeNode<K,V> left;//左節點
TreeNode<K,V> right; //右節點
//新添加的prev指針是爲了刪除方便
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next,
TreeNode<K,V> parent) {
super(hash, key, val, next);
this.parent = parent;
}
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
/**
* Returns the TreeNode (or null if not found) for the given key
* starting at given root.
*/
// 以當前節點 this 爲根節點開始遍歷查找,跟HashMap.TreeNode.find實現一樣
// 同 HashMap 的紅黑樹 find 操作
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
TreeNode<K,V> p = this;
do {
// 定義當前節點p的hash值ph、相對位置dir、pk、臨時節點 q
int ph, dir; K pk; TreeNode<K,V> q;
// pl 當前節點 p 的左節點, pr 當前節點 p 的右節點
TreeNode<K,V> pl = p.left, pr = p.right;
//如果key的hash值小於當前節點,取當前節點左邊的節點
if ((ph = p.hash) > h)
p = pl;
//如果key的hash值大於當前節點,取當前節點右邊的節點
else if (ph < h)
p = pr;
//如果key的hash值等於當前節點,直接返回當前節點,結束遞歸查找
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
//如果當前節點的左節點爲空,說明左邊已經查找完了,再去判斷右節點
else if (pl == null)
p = pr;
//如果當前節點的右節點爲空,說明右邊已經查找完了,就再去判斷左節點
//防止左右一邊爲空,提前退出的情況
else if (pr == null)
p = pl;
//6: 不採用hashcode的判斷大小的話,可以選擇compareTo自定義的判斷方法
//只需要自己實現key的Comparable就好了
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//如果當前節點和key的hashcode相等,但是key的值不等,並且沒有實現
//comparable的話,只能用最簡單的方法,先匹配右邊,匹配不到匹配左邊
//
// 當前節點的hash值與目標節點hash值相等,
// 且當前節點的key與目標key不相等(equals),
// 且左子節點與右子節點均不爲null,目標key沒有實現Comparable接口,
// 則直接在右子樹中查詢,這個方法並沒有在左子樹中循環,因爲這是一個遞歸方法,
// 先遍歷右子樹並判斷是否查找到,若無則將左子樹根節點作爲當前節點,
// 不用遍歷左子樹依然可以覆蓋全部情況
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
else
//右節點找不到,再遞歸查找左節點
p = pl;
} while (p != null);
}
//如果找不到,返回null
return null;
}
}
【3】TreeBin 代理操作 TreeNode 的節點
//TreeBin:代理操作TreeNode的節點
//TreeBin的hash值固定爲-2,它是ConcurrentHashMap中用於代理操作TreeNode的特殊節點,持有存儲實際數據的紅黑樹的根節點
//由於紅黑樹進行寫入操作,整個樹的結構可能會有很大的變化,這會對讀線程有很大的影響,所以TreeBin還要維護一個簡單讀寫鎖
//不會存儲真實數據,但是對紅黑樹的結構進行了管理
//
// 紅黑樹節點TreeNode實際上還保存有鏈表的指針,因此也可以用鏈表的方式進行遍歷讀取操作
// 自身維護一個簡單的讀寫鎖,不用考慮寫-寫競爭的情況
// 不是全部的寫操作都要加寫鎖,只有部分的put/remove需要加寫鎖
// 很多方法的實現和jdk1.8的ConcurrentHashMap.TreeNode裏面的方法基本一樣,可以互相參考
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root; // 紅黑樹根節點
volatile TreeNode<K,V> first; // 紅黑樹頭節點
volatile Thread waiter; // 獲得鎖失敗後,等待的線程
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock 正在持有寫鎖
static final int WAITER = 2; // set when waiting for write lock 正在等待獲得寫鎖
static final int READER = 4; // increment value for setting read lock 讀鎖計數
/**
* Tie-breaking utility for ordering insertions when equal
* hashCodes and non-comparable. We don't require a total
* order, just a consistent insertion rule to maintain
* equivalence across rebalancings. Tie-breaking further than
* necessary simplifies testing a bit.
*/
//該方法用於決定紅黑樹的方向
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
/**
* Creates bin with initial set of nodes headed by b.
*/
//用以b爲頭結點的鏈表創建一棵紅黑樹
TreeBin(TreeNode<K,V> b) {
super(TREEBIN, null, null, null);
this.first = b;
//節點 r 是根節點
TreeNode<K,V> r = null;
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
//創建根節點
if (r == null) {
x.parent = null;
x.red = false;
r = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = r;;) {
int dir, ph;
K pk = p.key;
//判斷紅黑樹方向
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
r = balanceInsertion(r, x);
break;
}
}
}
}
this.root = r;
assert checkInvariants(root);
}
/**
* Possibly blocks awaiting root lock.
*/
// 用於獲得寫鎖失敗時,讓當前線程進入等待
// 可能會阻塞寫線程,當寫線程獲取到寫鎖時,纔會返回
// ConcurrentHashMap的put/remove/replace方法本身就會鎖住TreeBin節點,這裏不會出現寫-寫競爭的情況
// 本身這個方法就是給寫線程用的,因此只用考慮讀鎖阻礙線程獲取寫鎖,不用考慮寫鎖阻礙線程獲取寫鎖
// 這個讀寫鎖本身實現得很簡單,處理不了寫-寫競爭的情況
// waiter要麼是null,要麼是當前線程本身
private final void contendedLock() {
boolean waiting = false;
for (int s;;) {
// 如果當前線程狀態獲得寫鎖了,直接返回
// ~WAITER是對WAITER進行二進制取反,當此時沒有線程持有讀鎖(不會有線程持有寫鎖)時,這個if爲真
if (((s = lockState) & ~WAITER) == 0) {
//嘗試獲取寫鎖
if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
// 在讀鎖、寫鎖都沒有被別的線程持有時,嘗試爲本寫線程獲取寫鎖,同時清空 WAITER 狀態的標識位
if (waiting)
// 獲取到寫鎖時,如果自己曾經註冊過 WAITER 狀態,將其清除
waiter = null;
return;
}
}
// 如果當前線程狀態變更爲等待成功,設置 waiting 爲 true,並把當前線程賦值給 waiter
// 有線程持有讀鎖(不會有線程持有寫鎖),並且當前線程不是 WAITER 狀態時,這個else if爲真
else if ((s & WAITER) == 0) {
// 嘗試佔據 WAITER 狀態標識位
if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
waiting = true;
waiter = Thread.currentThread();
}
}
// 線程在等待獲得鎖的狀態時,阻塞
// 有線程持有讀鎖(不會有線程持有寫鎖),並且當前線程處於 WAITER 狀態時,這個else if爲真
else if (waiting)
//阻塞當前線程
LockSupport.park(this);
}
}
/**
* Returns matching node or null if none. Tries to search
* using tree comparisons from root, but continues linear
* search when lock not available.
*/
//從根節點開始遍歷查找,找到“相等”的節點就返回它,沒找到就返回null
//當有寫線程加上寫鎖時,使用鏈表方式進行查找
final Node<K,V> find(int h, Object k) {
if (k != null) {
for (Node<K,V> e = first; e != null; ) {
int s; K ek;
// 兩種特殊情況下以鏈表的方式進行查找
// 1、有線程正持有寫鎖,這樣做能夠不阻塞讀線程
// 2、WAITER時,不再繼續加讀鎖,能夠讓已經被阻塞的寫線程儘快恢復運行或者讓某個寫線程不被阻塞
if (((s = lockState) & (WAITER|WRITER)) != 0) {
//此時寫線程持有寫鎖使用鏈表方式進行查找
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
e = e.next;
}
// 讀線程數量加1,讀狀態進行累加
else if (U.compareAndSwapInt(this, LOCKSTATE, s,
s + READER)) {
TreeNode<K,V> r, p;
try {
//紅黑樹方式查找
p = ((r = root) == null ? null :
r.findTreeNode(h, k, null));
} finally {
Thread w;
// 如果這是最後一個讀線程,並且有寫線程因爲讀鎖而阻塞,
// 那麼要通知寫線程,告訴寫線程可以嘗試獲取寫鎖了
// U.getAndAddInt(this, LOCKSTATE, -READER)這個操作是在更新之後返回lockstate的舊值
// 不是返回新值,相當於先判斷==,再執行減法
if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
(READER|WAITER) && (w = waiter) != null)
//讓被阻塞的寫線程運行起來,重新去嘗試獲取寫鎖
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
/**
* Finds or adds a node.
* @return null if added
*/
//用於實現ConcurrentHashMap.putVal
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
for (TreeNode<K,V> p = root;;) {
// 從root節點開始遍歷
int dir, ph; K pk;
if (p == null) {
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
// 通過比較hash大小確定添加元素的位置
// p hash 值大於 h,說明 p 在 h 的右邊
else if ((ph = p.hash) > h)
dir = -1;
// p hash 值小於 h,說明 p 在 h 的左邊
else if (ph < h)
dir = 1;
//要放進去key在當前樹中已經存在了(equals來判斷)
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
// key相同直接返回
return p;
//自己實現的Comparable的話,不能用hashcode比較了,需要用compareTo
else if ((kc == null &&
//得到key的Class類型,如果key沒有實現Comparable就是null
(kc = comparableClassFor(k)) == null) ||
//當前節點pk和入參k不等
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
// 有相同節點直接返回
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
// 比較 a 類與 b 類的 hashCode 從而確定紅黑樹方向
dir = tieBreakOrder(k, pk);
}
//緩存當前節點
TreeNode<K,V> xp = p;
//找到和當前hashcode值相近的節點(當前節點的左右子節點其中一個爲空即可)
//根據dir大小添加元素
if ((p = (dir <= 0) ? p.left : p.right) == null) {
TreeNode<K,V> x, f = first;
// x 爲新增節點
first = x = new TreeNode<K,V>(h, k, v, f, xp);
if (f != null)
// 新增的節點插入到鏈表的頭部
f.prev = x;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 二叉搜索樹新添加的節點,都是取代原來某個的NIL節點(空節點,null節點)的位置
// xp是新添加的節點的父節點,如果它是黑色的,新添加一個紅色節點就能夠保證x這部分的一部分路徑關係不變
// 紅黑樹中新增節點都是紅色的
if (!xp.red)
x.red = true;
else {
// 其他情況下會有樹的旋轉的情況出現,當讀線程使用紅黑樹方式進行查找時,
// 可能會因爲樹的旋轉,導致多遍歷、少遍歷節點,影響find的結果
// 除了那種最最簡單的情況,其餘的都要加寫鎖,讓讀線程用鏈表方式進行遍歷讀取
lockRoot();
try {
// 紅黑樹平衡處理, 參見 HashMap balanceInsertion
root = balanceInsertion(root, x);
} finally {
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
// /**
// * 比較obj的offset處內存位置中的值和期望的值,如果相同則更新,此更新是不可中斷的
// *
// * @param obj 需要更新的對象
// * @param offset obj中整型field的偏移量
// * @param expect 希望field中存在的值
// * @param update 如果期望值expect與field的當前值相同,設置filed的值爲這個新值
// * @return 如果field的值被更改返回true
// */
// public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
//獲得根節點的寫鎖
//對根節點加寫鎖,紅黑樹重構時需要加上寫鎖
private final void lockRoot() {
//cas 寫 LOCKSTATE 的狀態,如果是0,就寫入1,表示被佔用鎖
//如果鎖定失敗,將一直競爭,直到得到爲止
if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
//單獨抽象出一個方法,直到獲取到寫鎖這個調用纔會返回
contendedLock(); // offload to separate method
}
//釋放根節點的寫鎖
private final void unlockRoot() {
lockState = 0;
}
/**
* Removes the given node, that must be present before this
* call. This is messier than typical red-black deletion code
* because we cannot swap the contents of an interior node
* with a leaf successor that is pinned by "next" pointers
* that are accessible independently of lock. So instead we
* swap the tree linkages.
*
* @return true if now too small, so should be untreeified
*/
// 類似 HashMap.TreeNode.removeTreeNode
// 兩點區別:
// 1、返回值,紅黑樹的規模太小時,返回true,調用者再去進行樹->鏈表的轉化;
// 2、紅黑樹規模足夠,不用變換成鏈表時,進行紅黑樹上的刪除要加寫鎖
final boolean removeTreeNode(TreeNode<K,V> p) {
// next 當前節點的下一個節點
TreeNode<K,V> next = (TreeNode<K,V>)p.next;
// pred 當前節點的上一個節點
TreeNode<K,V> pred = p.prev; // unlink traversal pointers
TreeNode<K,V> r, rl;
// 以下代碼爲刪除當前節點操作
if (pred == null)
first = next;
else
pred.next = next;
if (next != null)
next.prev = pred;
if (first == null) {
root = null;
return true;
}
if ((r = root) == null || r.right == null || // too small
(rl = r.left) == null || rl.left == null)
// 紅黑樹太小直接返回, 調用者再使用遍歷鏈表的方式刪除節點
return true;
// 加鎖,進行紅黑樹的刪除操作,參見 HashMap removeTreeNode 方法
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;
}
/* ------------------------------------------------------------ */
// Red-black tree methods, all adapted from CLR
// 紅黑樹左旋操作方法
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
// 紅黑樹右旋操作方法
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
// 紅黑樹插入節點後的平衡處理方法
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)
return root;
if (xp == (xppl = xpp.left)) {
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
// 紅黑樹節點刪除後的平衡處理
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> xp, xpl, xpr;;) {
if (x == null || x == root)
return root;
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (x.red) {
x.red = false;
return root;
}
else if ((xpl = xp.left) == x) {
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null)
x = xp;
else {
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true;
x = xp;
}
else {
if (sr == null || !sr.red) {
if (sl != null)
sl.red = false;
xpr.red = true;
root = rotateRight(root, xpr);
xpr = (xp = x.parent) == null ?
null : xp.right;
}
if (xpr != null) {
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null)
sr.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateLeft(root, xp);
}
x = root;
}
}
}
else { // symmetric
if (xpl != null && xpl.red) {
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
x = xp;
else {
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true;
x = xp;
}
else {
if (sl == null || !sl.red) {
if (sr != null)
sr.red = false;
xpl.red = true;
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
/**
* Recursive invariant check
*/
// 遞歸檢查一些關係,確保構造的是正確無誤的紅黑樹
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K,V>)t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
// 初始化 unsafe 實例 U
private static final sun.misc.Unsafe U;
private static final long LOCKSTATE;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = TreeBin.class;
LOCKSTATE = U.objectFieldOffset
(k.getDeclaredField("lockState"));
} catch (Exception e) {
throw new Error(e);
}
}
}
【4】ForwardingNode 轉發節點
//擴容轉發節點,放置此節點, 對原有hash槽的操作轉化到新的 nextTable 上
//ForwardingNode是一種臨時節點,在擴容進行中才會出現,hash值固定爲-1,並且它不存儲實際的數據數據
//如果舊數組的一個hash桶中全部的節點都遷移到新數組中,舊數組就在這個hash桶中放置一個ForwardingNode
//讀操作或者迭代讀時碰到ForwardingNode時,將操作轉發到擴容後的新的table數組上去執行
//寫操作碰見它時,則嘗試幫助擴容
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
// ForwardingNode的查找操作,直接在新數組nextTable上去進行查找
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
// 避免深度遞歸
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
for (;;) {
int eh; K ek;
//第一個節點就是要找的節點,直接返回
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
if (eh < 0) {
//繼續碰見ForwardingNode的情況,這裏相當於是遞歸調用一次本方法
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
else
//碰見特殊節點,調用其find方法進行查找
return e.find(h, k);
}
//普通節點直接循環遍歷鏈表
if ((e = e.next) == null)
return null;
}
}
}
}
【5】ReservationNode 保留節點
// ReservationNode : 保留節點, 空節點
// hash值固定爲-3,就是個佔位符,不會保存實際的數據
// computeIfAbsent and compute 中會使用的佔位節點
// 爲什麼需要這個節點
// 因爲正常的寫操作,都會想對hash桶的第一個節點進行加鎖,但是null是不能加鎖
// 所以就要new一個佔位符出來,放在這個空hash桶中成爲第一個節點,把佔位符當鎖的對象,這樣就能對整個hash桶加鎖了
// put/remove不使用ReservationNode
// put直接使用cas操作,remove直接不操作,都不用加鎖
// 但是computeIfAbsent和compute這個兩個方法在碰見這種特殊情況時稍微複雜些,代碼多一些,不加鎖不好處理
// 所以需要ReservationNode來幫助完成對hash桶的加鎖操作
static final class ReservationNode<K,V> extends Node<K,V> {
ReservationNode() {
super(RESERVED, null, null, null);
}
Node<K,V> find(int h, Object k) {
return null;
}
}
【6】數組初始化
/**
* 初始化數組table
* 如果sizeCtl小於0,說明別的數組正在進行初始化,則讓出執行權
* 如果sizeCtl大於0的話,則初始化一個大小爲sizeCtl的數組
* 否則的話初始化一個默認大小(16)的數組
* 然後設置sizeCtl的值爲數組長度的3/4
*/
//
// sizeCtl = -1,表示有線程正在進行真正的初始化操作
// sizeCtl = -(1 + nThreads),表示有nThreads個線程正在進行擴容操作
// sizeCtl > 0,表示接下來的真正的初始化操作中使用的容量,或者初始化/擴容完成後的threshold
// sizeCtl = 0,默認值,此時在真正的初始化操作中使用默認容量
//
//初始化table,使用sizeCtl來保證數組被初始化一次
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
//通過自旋保證初始化成功
//當 table 爲空則一直循環直到初始化成功過
//第一次put的時候,table還沒被初始化,進入while
while ((tab = table) == null || tab.length == 0) {
// 小於0代表有線程正在初始化,釋放當前 CPU 的調度權,重新發起鎖的競爭
if ((sc = sizeCtl) < 0)
//Thread.yield() 方法,使當前線程由執行狀態,變成爲就緒狀態,讓出cpu時間,
//在下一個線程執行時候,此線程有可能被執行,也有可能沒有被執行
//
//真正的初始化是要禁止併發的,保證tables數組只被初始化一次,
//但是又不能切換線程,所以用yeild()暫時讓出CPU
Thread.yield(); // lost initialization race; just spin
//保證當前只有一個線程在初始化,-1代表當前只有一個線程能初始化
//保證了數組的初始化的安全性
//
// unsafe.compareAndSwapInt :
// 第一個參數爲需要改變的對象,第二個爲偏移量(即之前求出來的valueOffset的值),
// 第三個參數爲期待的值,第四個爲更新後的值
// 作用即爲若調用該方法時,value的值與expect這個值相等,那麼則將value修改爲update這個值,並返回一個true,
// 如果調用該方法時,value的值與expect這個值不相等,那麼不做任何操作,並範圍一個false
//SIZECTL:表示當前對象的內存偏移量,sc表示期望值,-1表示要替換的值,設定爲-1表示要初始化表了
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//很有可能執行到這裏的時候,table已經不爲空了
//雙重 check
if ((tab = table) == null || tab.length == 0) {
//初始化數組長度
//指定了大小的時候就創建指定大小的Node數組,否則創建默認大小(16)的Node數組
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//初始化 Node 數組
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
//初始化後,sizeCtl長度爲數組長度的3/4
sizeCtl = sc;
}
break;
}
}
return tab;
}
【7】讀寫 table 數組方法
// volatile讀取table[i]
// 該方法獲取對象中offset偏移地址對應的整型field的值,支持volatile load語義
// public native Object getObjectVolatile(Object o, long offset);
// 雖然table數組本身是增加了volatile屬性,但是“volatile的數組只針對數組的引用具有volatile的語義,而不是它的元素”
// 所以如果有其他線程對這個數組的元素進行寫操作,那麼當前線程來讀的時候不一定能讀到最新的值
@SuppressWarnings("unchecked")
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);
}
// CAS更新table[i],也就是Node鏈表的頭節點,或者TreeBin節點(它持有紅黑樹的根節點)
// CAS,如果對象偏移量上的值=期待值,更新爲x,返回true否則false
// public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
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);
}
// volatile寫入table[i]
// 設置obj對象中offset偏移地址對應的object型field的值爲指定值
// 這是一個有序或者有延遲的putObjectVolatile方法,並且不保證值的改變被其他線程立即看到
// 只有在field被volatile修飾並且期望被意外修改的時候用纔有用
// public native void putOrderedObject(Object obj, long offset, Object value);
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
致謝
本博客爲博主的學習實踐總結,並參考了衆多博主的博文,在此表示感謝,博主若有不足之處,請批評指正。