【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);
}
致谢
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。