get()方法
流程总结
1.计算key的hash值寻址到指定桶位
2.当前桶位没有元素直接返回NULL。
3.当前桶位如果是fwd节点或者是红黑树节点,则调用各自的find()
方法。
4.当前桶位不是fwd节点也不是红黑树节点,则遍历桶位,进行查找。
5.查找元素没有任何的加锁
操作。
源码解析
/*
* 根据key进行查找获取value值
*/
public V get(Object key) {
/*
* tab 引用table
* e 当前元素
* p 目标元素
* eh 当前元素hash
* ek 当前元素key
*/
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//扰动运算后得到更散列的hash值
int h = spread(key.hashCode());
/*
* 进入if语句内部的条件
* 条件一: table不为NULL
* 条件二: 当前桶位有元素
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
/*
* 对比与头结点的key是否完全一致
* 1.比较key的hash值是否一致
* 2.比较key的内存地址是否一致 或 进行equals的返回true
* 同时满足这两个条件才能认为key完全一致
*/
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
//完全一致 直接返回value
return e.val;
}
/*
* hash值小于0,有两种情况
* 1. hash = -1,表示当前桶位是FWD节点,表示当前桶位中的节点已经被迁移了
* 2. hash = -2, 表示当前桶位是TreeBin节点,
*
* 如果是FWD节点的话,会进入对应的FWD中定义的find方法进行查找,如果是红黑树
* 会进入TreeNode内部的find方法进行查找(多态)
*/
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
/*
* 当前桶位是一个链表,直接遍历每一个节点找到一个key完全相同的节点
* 找到返回value即可。
*/
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
ForwardingNode的find()方法
上面在调用get()方法进行查找的时候,如果查找到当前桶位的节点的hash值为-1,说明当前桶位的可能是fwd节点或者是红黑树节点,就要到指定的结构中去查找,这里我们分析一下当hash值为-1时,去ForwardingNode节点查找的方法,关于去红黑树中查找的源码解析,放到后续的TreeBin的源码解析中讲解。
流程总结
1.直接去nextTable中查询数据,经过寻址算法后发现当前桶位没有元素,(可能原桶位中的元素都是经过扩容后都是高(低)位,而当前寻址得到的桶位刚好相反)
2.如果发现当前桶位还是fwd节点 (可能高并发下又发生了扩容) 那么继续去新表查询。
3.如果发现当前桶位是红黑树,那么调用红黑树的find方法去查询
4.否则当前是链表,遍历链表查询即可。
5.find()方法没有加锁过程
Node<K,V> find(int h, Object k) {
/*
* tab一定不为NULL。(详细解析见transfer方法)
* 将新表赋值给tab
*/
Node<K,V>[] tab = nextTable;
//自旋
outer: for (;;) {
/*
* e 表示新表中寻址算法得到的桶位头结点
* n 表示新表的长度
*/
Node<K,V> e; int n;
/*
* 条件1 2 3 永远不成立
* 条件4.
* true -> 根据hash值去新表中拿数据时,新表中的桶位有数据
* false -> 新表的目标桶位为NULL.
*/
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null; //新表桶位没有数据 直接返回NULL.
/*
* 前置条件: 扩容后的表,对应hash的桶位不是NULL,e为此桶位的头结点
* e可能是哪些类型
* 1.node类型
* 2.TreeBin类型
* 3.FWD类型(高并发下扩容后再次触发扩容)
*/
//自旋
for (;;) {
/*
* eh:新扩容后表指定桶位的当前节点的hash
* ek:新扩容后表指定桶位的当前节点的key
*/
int eh; K ek;
/*
* 当前桶位查找成功
*/
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
/*
* eh < 0
* 1.可能是TreeBin节点
* 2.可能是FWD节点(新扩容后的表,可能在并发很大的情况下,再次扩容)
*/
if (eh < 0) {
//fwd节点 继续进行外层循环,继续在新表中查找。
if (e instanceof ForwardingNode) {
//在新的fwd节点中拿到新表
tab = ((ForwardingNode<K,V>)e).nextTable;
continue outer;
}
//调用红黑树节点的查询。
else
return e.find(h, k);
}
//遍历到链表末尾也没有找到目标节点,返回NULL.
if ((e = e.next) == null)
return null;
}
}
}
}
remove方法
流程总结
- remove底层调用的是replaceNode方法,传入的后两个参数都是NULL,表示当前的替换操作是删除。
- 计算hash值,自旋去尝试删除
- 寻址后桶位上没有元素,返回NULL。
- 桶位上是链表,则遍历链表查找元素进行删除.
- 桶位上是fwd节点,表示正在扩容,先尝试帮助扩容,然后继续循环尝试删除
- 桶位上形成了红黑树,遍历树查找元素,然后删除,删除后树中元素节点数较少,则
退化为链表
- 如果确实删除了元素 (非value替换),调用addCount()将元素个数-1,
- 删除过程加锁
(sycnhronized)
锁定当前桶位的头结点。
public V remove(Object key) {
//调用了替换节点的方法
return replaceNode(key, null, null);
}
||
||
/
/*
* @param Object key -> 表示要替换的key
* @param V value -> 要替换的目标值
* @param Object cv(compare value) -> 如果cv不为NULL,则需要既对比key还要对比
* cv,这两个参数都一致,才能替换目标值
*/
final V replaceNode(Object key, V value, Object cv) {
//计算key的hash
int hash = spread(key.hashCode());
//引用哈希表
Node<K,V>[] tab = table;
//自旋
for (;;) {
/*
* f表示桶位头节点
* n 表示table长度
* i表示hash寻址后命中的下标
* fh表示桶位头节点的hash值
*/
Node<K,V> f; int n, i, fh;
/*
* CASE1
* 如果table为NULL,或者当前寻址后命中的桶位没有元素,
* 直接break循环。
*/
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
/*
* CASE2
* 当前桶位是fwd节点,说明当前table正在扩容中,
* 由于当前是写操作,需要尝试帮助table扩容。
*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
/*
* CASE3
* 当前桶位是链表或者红黑树
*/
else {
//保留替换之前的value
V oldVal = null;
//校验标记 用于判断是否锁错对象,从而进入下一次的自旋。
boolean validated = false;
//锁定当前桶位的头结点(分段锁思想)
synchronized (f) {
/*
* 判断锁定的对象是否是当前桶位头节点,
* 防止当前线程加锁之前,其他线程修改过桶位的头节点。
*/
if (tabAt(tab, i) == f) {
//哈希值大于0,说明桶位为正常的Node节点(或者Node链表)
if (fh >= 0) {
//validated赋值为true。
validated = true;
/*
* e 表示当前循环的元素
* pred 表示上一个元素
*/
Node<K,V> e = f, pred = null;
for (;;) {
//当前桶位的key。
K ek;
//找到了指定key
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//将找到的指定节点的val赋值给ev
V ev = e.val;
/*
* cv == null -> true 表示这是一个删除操作
*
* cv == ev -> true 表示这是一个替换操作
*/
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
//直接将value替换
if (value != null)
e.val = value;
/*
* 当前节点不是头结点,并且这是一次删除操作
* 直接在链表中将e删除即可
*/
else if (pred != null)
pred.next = e.next;
/*
* e是头结点,调用setTab方法将e.next设置为
* 头结点即可,变向的将e删除。
*/
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
//没有找到指定节点,退出。
if ((e = e.next) == null)
break;
}
}
//红黑树
else if (f instanceof TreeBin) {
//修改标志位
validated = true;
//转换为指定的TreeBin节点
TreeBin<K,V> t = (TreeBin<K,V>)f;
/*
* r 表示红黑树根节点
* p 表示红黑树中查找到的对应的key一致的Node。
*/
TreeNode<K,V> r, p;
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。
oldVal = pv;
//value不为NULL,替换
if (value != null)
p.val = value;
//删除
else if (t.removeTreeNode(p))
/*
* 如果删除后树的元素个数较少则退化成链表
* t.removeTreeNode(p)这个方法返回true表示删
* 除节点后树的元素个数是否较少(boolean)
*/
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
/*
* 当其他线程修改过桶位的头节点时,说明当前线程锁错了对象
* 此时validated为false,不会进入到下面的if的逻辑中终止,则会进入下次自旋
*
* 如果此时validated为true,说明进入过真正的删除逻辑中,然后判断是否真正
* 的删除了节点,是的话则调用addCount()将元素数量减一。然后结束方法。
*/
if (validated) {
//oldVal不为NULL,说明操作成功。
if (oldVal != null) {
//value == null 说明这是一次删除操作,更新节点个数
if (value == null)
addCount(-1L, -1);
//返回旧值
return oldVal;
}
break;
}
}
}
return null;
}
最后
以上就是呆萌豌豆最近收集整理的关于ConcurrentHashMap源码解析5.get()&remove()方法的全部内容,更多相关ConcurrentHashMap源码解析5内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复