我是靠谱客的博主 俏皮小笼包,最近开发中收集的这篇文章主要介绍ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
ConcurrentHashMap_get与remove方法源码分析
文章目录
- ConcurrentHashMap_get与remove方法源码分析
- 1. get() 方法
- 1.1 ForwardingNode的find() 方法
- 2. remove() 方法
1. get() 方法
与普通的Map一样,通过key查找元素。
在ConcurrentHashMap的get方法中也没有任何加锁逻辑。
/**
* 返回指定键映射到的值,如果该映射不包含键的映射,则返回null。
* 更正式地说,如果这个映射包含从键k到值v的映射,使得key.equals(k),
* 那么这个方法返回v;否则将返回null。(最多只能有一个这样的映射。)
* @param key 要返回其关联值的键
* @return
*/
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());
// 条件1:table不为 null
// 条件2:长度大于0
// 条件3:桶位上的元素不为 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方法进行查找(fwd节点和treeBin都继承了Node节点并重写了find方法)
*/
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;
}
流程总结:
- 计算key的hash值
- 根据key的hash计算桶位位置,判断桶位上是否有元素
- 当前桶位如果是fwd节点或者是红黑树节点,则调用各自的
find()
方法。 - 当前桶位不是fwd节点也不是红黑树节点,则遍历桶位,进行查找。
在get
方法中调用了FWD和TreeBin中的find
方法。
下面对find
方法进行分析。
1.1 ForwardingNode的find() 方法
同样没有加锁逻辑。
Node<K,V> find(int h, Object k) {
// 循环以避免转发节点上的任意深度递归
// tab: 新建散列表的引用
outer: for (Node<K,V>[] tab = nextTable;;) {
//------------------外轮自旋开始--------------------
// 新散列表上元素的引用
// 新散列表长度
Node<K,V> e; int n;
// 条件1:永远不成立
// 条件2:永远不成立
// 条件3:永远不成立
// 条件4:在新扩容表中重新路由定位 hash 对应的头结点
// true -> 1.在 oldTable 中对应的桶位在迁移之前就是 null
// false -> 2.扩容完成后,有其它写线程,将此桶位设置为 null
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
//------------------内轮自旋开始--------------------
// 前置条件:扩容后的表对应hash的桶位一定不是null,e为此桶位的头结点
// e可能表示的类型:
// 1.node 类型
// 2.TreeBin 类型
// 3.FWD 类型
for (;;) {
// eh 新扩容后表指定桶位的当前节点的hash
// ek 新扩容后表指定桶位的当前节点的key
int eh; K ek;
// 如果成立:表示出入的key命中新扩容的表中的数据
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
// 返回命中元素
return e;
// eh < 0 有两种情况
// 1. 表示TreeBin类型
// 2. FWD类型(新扩容的表,在并发很大的情况下再次发生了扩容,所以可能在此方法再次拿到FWD类型)
if (eh < 0) {
// 判断是否是FWD结点
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;
// 调到外轮自旋
continue outer;
}
else
// 说明此桶位为 TreeBin 节点,使用TreeBin.find 查找红黑树中相应节点。
return e.find(h, k);
}
// 前置条件:当前桶位头结点 并没有命中查询,说明此桶位是链表
// 1. 将当前元素指向链表的下一个元素
// 2. 判断当前元素的下一个位置是否为空
// true -> 说明迭代到链表末尾,未找到对应的数据,返回Null
if ((e = e.next) == null)
return null;
}
//------------------内轮自旋结束--------------------
}
//------------------外轮自旋结束--------------------
}
}
2. remove() 方法
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;
}
方法流程总结:
remove
底层调用的是replaceNode
方法,传入的后两个参数都是NULL,表示当前的替换操作是删除。- 计算hash值,自旋去尝试删除
- 寻址后桶位上没有元素,返回NULL。
- 桶位上是链表,则遍历链表查找元素进行删除.
- 桶位上是fwd节点,表示正在扩容,先尝试帮助扩容,然后继续循环尝试删除
- 桶位上形成了红黑树,遍历树查找元素,然后删除,删除后树中元素节点数较少,则退化为链表
- 如果确实删除了元素 (非
value
替换),调用addCount()
将元素个数-1, - 删除过程加锁(
sycnhronized
)锁定当前桶位的头结点。
最后
以上就是俏皮小笼包为你收集整理的ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析的全部内容,希望文章能够帮你解决ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复