本文重点解析ConcurrentHashMap.get()方法,及其代码中包含的所有涉及到的子方法。其中,spread()和find()是比较复杂的两个方法,下面会重点讲解。
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
//tab: table引用
//e: 游标节点
//p: 查找到的节点
//n: table长度
//eh: 游标节点hash值
//ek: 游标节点key
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//获取rehash后的hash值
int h = spread(key.hashCode());
//true -> 当前table不为空 且 在桶位上匹配到节点
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
//指定桶位上的节点hash值与需查找的节点的hash值相等
if ((eh = e.hash) == h) {
//true -> 表示需要查找的节点就是该桶位上的头结点
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
//返回匹配到的桶位元素的value
return e.val;
}
//指定桶位上的节点hash值是负数 说明当前桶位是 FWD桶位 或 TreeBin桶位
else if (eh < 0)
//根据e节点所属的类型(FWD or TreeBin) 调用各自重写的find方法,并返回匹配到的节点的value
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;
}
spread()方法的作用是将key的hash值再rehash一次,将高16位也参与到计算中,保证得到的值更加散列。
/**
* Spreads (XORs) higher bits of hash to lower and also forces top
* bit to 0. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*
* HASH_BITS 将得出的结果取正数
* 其它操作都与hashmap一致,唯一不同就是最后结果要取正
*
* h: 1100 0011 1010 0101 0001 1100 0001 1110
* h >>> 16: 0000 0000 0000 0000 1100 0011 1010 0101
* ^: 1100 0011 1010 0101 1101 1111 1011 1011
* HASH_BITS: 0111 1111 1111 1111 1111 1111 1111 1111
* &: 0100 0011 1010 0101 1101 1111 1011 1011
*/
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
e.find()方法需要根据调用对象e的类型(ForwardingNode 或者 TreeBin)决定调用的是哪个find()方法。
1、ForwardingNode.find()
/**
* A node inserted at head of bins during transfer operations.
*/
static final class ForwardingNode<K,V> extends Node<K,V> {
//新表的引用
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
//hash值固定为-1
super(MOVED, null, null, null);
this.nextTable = tab;
}
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
//获取nextTable引用
//注意: 如果当前访问的节点是FWD结点,说明数组正在进行扩容,因此会去nextTable获取数据
Node<K,V>[] tab = nextTable;
//外层自旋
outer: for (;;) {
//e: 游标节点
//n: table数组长度
Node<K,V> e; int n;
if (k == null || //传入的key为null
tab == null || //nextTable为null
(n = tab.length) == 0 || //nextTable中没数据
(e = tabAt(tab, (n - 1) & h)) == null) //nextTable指定桶位上为null
return null;
//内层自旋
for (;;) {
//eh: 当前游标节点的hash值 初始值是当前桶位上的头结点的hash值
//ek: 当前游标节点的key 初始值是当前桶位上的头结点的key
int eh; K ek;
if ((eh = e.hash) == h && //当前游标节点的hash值与需查找节点的hash值一致
((ek = e.key) == k || (ek != null && k.equals(ek)))) //当前游标节点的key与需查找节点的key一致
//返回当前游标节点
return e;
//当前游标的节点hash值是负数 说明当前桶位是 FWD桶位 或 TreeBin桶位
if (eh < 0) {
//如果当前桶位节点是FWD节点
//为什么上面已经赋值了nextTable 这里还要再赋值一次?防止nextTable再次触发了扩容,导致当前节点是个FWD节点
if (e instanceof ForwardingNode) {
//将迁移的table赋值给tab
tab = ((ForwardingNode<K,V>)e).nextTable;
//回到外层自旋,重新开始匹配
continue outer;
}
else
//如果当前桶位节点是TreeBin 调用TreeBin.find()
return e.find(h, k);
}
//遍历当前桶位上的链表
if ((e = e.next) == null)
//如果到末尾还未匹配到就返回null
return null;
}
}
}
}
2、ForwardingNode.find()
/**
* Returns matching node or null if none. Tries to search
* using tree comparisons from root, but continues linear
* search when lock not available.
*/
final Node<K,V> find(int h, Object k) {
//h: 需要查询的节点的hash值
//k: 需要查询的节点的key
if (k != null) {
//遍历first指针指向的双向链表
for (Node<K,V> e = first; e != null; ) {
//s: lockState临时状态
//ek: 当前循环节点的key
int s; K ek;
//(WAITER|WRITER) -> 0010 | 0001 = 0011
//lockState & 0011 != 0 true -> 表示后两位不为0,说明有等待者线程 或 有写线程在加锁,需要从链表读取数据
if (((s = lockState) & (WAITER|WRITER)) != 0) {
//true -> 匹配到需要查找的元素
if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
//e指向下一个元素
e = e.next;
}
//前置条件: 没有等待者线程 或 没有写线程
//true -> 添加读锁成功 读锁是共享锁 从红黑树中读取数据
else if (U.compareAndSwapInt(this, LOCKSTATE, s,s + READER)) {
//r: 根节点
//p: 查询匹配到的节点
TreeNode<K,V> r, p;
try {
//从根节点开始往下查找
p = ((r = root) == null ? null : r.findTreeNode(h, k, null));
} finally {
//w: 表示等待者线程
Thread w;
//(READER|WAITER) -> 0100 | 0010 = 0110 => 6 表示当前有一个读线程和一个等待中的写线程
//U.getAndAddInt(this, LOCKSTATE, -READER) 注意:这里是先get到LOCKSTATE的值与(READER|WAITER)对比后再 -4 释放读锁
//当前线程为最后一个读线程
//(w = waiter) != null true -> 有写线程在等待读操作全部结束
if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER|WAITER) && (w = waiter) != null)
//唤醒等待中的写线程
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
上面还涉及到TreeNode.findTreeNode()方法,本文也给出详细的解析。
/**
* Returns the TreeNode (or null if not found) for the given key
* starting at given root.
*/
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
//游标节点 初始值为调用该方法的节点
TreeNode<K,V> p = this;
do {
//ph: 当前游标节点的hash值
//dir: 待插入节点位于当前游标节点的 左边 或 右边
//pk: 当前游标节点的key
//q: 匹配到的节点
int ph, dir; K pk; TreeNode<K,V> q;
//pl: 当前游标节点的左子节点
//pr: 当前游标节点的右子节点
TreeNode<K,V> pl = p.left, pr = p.right;
//CASE1: 当前游标节点的hash值 > 待插入节点的hash值
if ((ph = p.hash) > h)
p = pl;
//CASE2: 当前游标节点的hash值 < 待插入节点的hash值
else if (ph < h)
p = pr;
//前置条件: 当前游标节点的hash值 == 待插入节点的hash值
//CASE3: 当前游标节点的key == 待插入的节点的key
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
//CASE4: 当前游标节点的左子节点是null
else if (pl == null)
p = pr;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
//CASE4: 当前游标节点的右子节点是null
else if (pr == null)
p = pl;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
//CASE5: 获取待插入节点的key的class类型 并计算出dir
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
// 4、无法通过待插入节点key的class类型计算dir
//CASE6: 遍历当前游标节点的整个右子树
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
// 4、无法通过待插入节点key的class类型计算dir
// 5、遍历当前游标节点的整个右子树还是未找到
//CASE7: 从左子节点开始继续找
else
p = pl;
} while (p != null);
}
return null;
}
}