【093期】面试官:多线程环境下 HashMap为什么会出现死循环?

table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

//查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resize

if (size++ >= threshold)

resize(2 * table.length);

}

如果现在size已经超过了threshold,那么就要进行resize操作,新建一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中。

调整Hash表大小resize:

void resize(int newCapacity)

{

Entry[] oldTable = table;

int oldCapacity = oldTable.length;

//创建一个新的Hash Table

Entry[] newTable = new Entry[newCapacity];

//将Old Hash Table上的数据迁移到New Hash Table上

transfer(newTable);

table = newTable;

threshold = (int)(newCapacity * loadFactor);

}

当table[]数组容量较小,容易产生哈希碰撞,所以,Hash表的尺寸和容量非常的重要。

一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,这个过程称为resize。

多个线程同时往HashMap添加新元素时,多次resize会有一定概率出现死循环,因为每次resize需要把旧的数据映射到新的哈希表,这一部分代码在HashMap#transfer() 方法,如下:

void transfer(Entry[] newTable)

{

Entry[] src = table;

int newCapacity = newTable.length;

//下面这段代码的意思是:

//  从OldTable里摘一个元素出来,然后放到NewTable中

for (int j = 0; j < src.length; j++) {

Entry<K,V> e = src[j];

if (e != null) {

src[j] = null;

do {

Entry<K,V> next = e.next;//取出第一个元素

int i = indexFor(e.hash, newCapacity);

e.next = newTable[i];

newTable[i] = e;

e = next;

} while (e != null);

}

}

}

标红代码是导致多线程使用hashmap出现CUP使用率骤增,出现死循环,从而多个线程阻塞的罪魁祸首。

3、图解HashMap死循环:

正常的ReHash的过程(单线程):假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。

最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。接下来的三个步骤是Hash表 resize成4,然后所有的<key,value> 重新rehash的过程。

并发下的Rehash(多线程)

1)假设我们有两个线程。

do {

Entry<K,V> next = e.next; // <–假设线程一执行到这里就被调度挂起了,执行其他操作

int i = indexFor(e.hash, newCapacity);

e.next = newTable[i];

newTable[i] = e;

e = next;

} while (e != null);

而我们的线程二执行完成了。于是我们有下面的这个样子:

注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。在这里线程一变成了操作经过线程二操作后的HashMap。

2)线程一被调度回来执行。

  • 先是执行 newTalbe[i] = e;

  • 然后是e = next,导致了e指向了key(7)

  • 而下一次循环的next = e.next导致了next指向了key(3)

3)一切安好。

线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。这个元素所在的位置上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,而先前加入的放在链尾。

4)环形链接出现。

e.next = newTable[i] 导致  key(3).next 指向了 key(7)

注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

于是,当我们的线程一调用到,HashTable.get(11)时,悲剧就出现了——Infinite Loop。

这里介绍了在多线程下为什么HashMap会出现死循环,不过在真实的生产环境下,不会使用线程不安全的HashMap的。

作者:powerfuler

blog.csdn.net/dingjianmin/article/details/79780350

期往精选  点击标题可跳转

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。

这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来

目录:

部分内容截图:


《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
ava基础核心总结笔记》,特意跟大家分享出来

目录:

[外链图片转存中…(img-TWUAwWos-1712248882621)]

部分内容截图:

[外链图片转存中…(img-Vhkc0jHQ-1712248882622)]

[外链图片转存中…(img-8wbsEiVE-1712248882622)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值