千家信息网

怎么理解JAVA HASH MAP的死循环

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章主要讲解了"怎么理解JAVA HASH MAP的死循环",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么理解JAVA HASH MAP的死循环
千家信息网最后更新 2026年11月01日怎么理解JAVA HASH MAP的死循环

这篇文章主要讲解了"怎么理解JAVA HASH MAP的死循环",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么理解JAVA HASH MAP的死循环"吧!

问题的症状

从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题。后来,我们的程序性能有问题,所以需要变成多线程的,于是,变成多线程后到了线上,发现程序经常占了100%的CPU,查看堆栈,你会发现程序都Hang在了HashMap.get()这个方法上了,重启程序后问题消失。但是过段时间又会来。而且,这个问题在测试环境里可能很难重现。

我们简单的看一下我们自己的代码,我们就知道HashMap被多个线程操作。而Java的文档说HashMap是非线程安全的,应该用ConcurrentHashMap。

但是在这里我们可以来研究一下原因。

Hash表数据结构

我需要简单地说一下HashMap这个经典的数据结构。

HashMap通常会用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算法通过key算出这个数组的下标i,然后就把这个插到table[i]中,如果有两个不同的key被算在了同一个i,那么就叫冲突,又叫碰撞,这样会在table[i]上形成一个链表。

我们知道,如果table[]的尺寸很小,比如只有2个,如果要放进10个keys的话,那么碰撞非常频繁,于是一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。

所以,Hash表的尺寸和容量非常的重要。一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大Hash表的尺寸,但是这样一来,整个Hash表里的无素都需要被重算一遍。这叫rehash,这个成本相当的大。

相信大家对这个基础知识已经很熟悉了。

HashMap的rehash源代码

下面,我们来看一下Java的HashMap的源代码。

Put一个Key,Value对到Hash表中:

public V put(K key, V value){......//算Hash值int hash = hash(key.hashCode());int i = indexFor(hash, table.length);//如果该key已被插入,则替换掉旧的value (链接操作)for (Entry e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;//该key不存在,需要增加一个结点addEntry(hash, key, value, i);return null;}

检查容量是否超标

void addEntry(int hash, K key, V value, int bucketIndex){Entry e = table[bucketIndex];table[bucketIndex] = new Entry(hash, key, value, e);//查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resizeif (size++ >= threshold)resize(2 * table.length);}

新建一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中。

void resize(int newCapacity){Entry[] oldTable = table;int oldCapacity = oldTable.length;......//创建一个新的Hash TableEntry[] newTable = new Entry[newCapacity];//将Old Hash Table上的数据迁移到New Hash Table上transfer(newTable);table = newTable;threshold = (int)(newCapacity * loadFactor);}

迁移的源代码,注意高亮处:

void transfer(Entry[] newTable){Entry[] src = table;int newCapacity = newTable.length;//下面这段代码的意思是:// 从OldTable里摘一个元素出来,然后放到NewTable中for (int j = 0; j < src.length; j++) {Entry e = src[j];if (e != null) {src[j] = null;do {Entry next = e.next;int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;} while (e != null);}}}

好了,这个代码算是比较正常的。而且没有什么问题。

正常的ReHash的过程

我假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。 最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。 接下来的三个步骤是Hash表 resize成4,然后所有的 重新rehash的过程

并发下的Rehash

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

我们再回头看一下我们的 transfer代码中的这个细节:

do {Entry 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后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。

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

先是执行 newTalbe[i] = e;

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

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

3)一切安好。

线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。

JAVA HASHMAP的死循环

4)环形链接出现。

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

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

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

感谢各位的阅读,以上就是"怎么理解JAVA HASH MAP的死循环"的内容了,经过本文的学习后,相信大家对怎么理解JAVA HASH MAP的死循环这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

线程 指向 问题 循环 代码 数据 程序 尺寸 容量 数组 源代码 算法 学习 两个 内容 原因 就是 性能 数据结构 环形 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 云之行互联网科技 科技互联网企业 百单互联网科技有限公司 互联网科技logo 广州紫鲸互联网科技 互联网科技的未来发展 亿人帮互联网科技 人人互联网科技是培训 互联网科技创新企业排名 互联网科技项目计划书材料类 保险科技服务互联网营销体系 伊宁互联网科技做什么的 互联网金融科技公司的优势 21世纪互联网科技成就 半导体行业属于互联网高科技类吗 互联网金融运营模式中的金融科技 互联网科技手抄报图片 瑞安市蟠桃会互联网科技有限公司 宁波允成互联网科技有限公司 小牛互联网科技有限公司地址 高科技与互联网 互联网科技缩写 西藏七维空间互联网科技有限公司 乌鲁木齐新华智原互联网科技学校 互联网科技的全部知识 下一代互联网重大应用科技园 科技互联网行业投资研究 版纳互联网科技选哪家 互联网领先科技活动 知域互联网科技有限公司官网
0