JDK1.7和JDK1.8中HashMap为什么是线程不安全的?
https://blog.csdn.net/swpu_ocean/article/details/88917958
HashMap
的线程不安全体现在会造成死循环、数据丢失、数据覆盖这些问题。其中死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而1.8中仍会有数据覆盖这样的问题。
扩容引发的线程不安全
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; // 如果之前的HashMap已经扩充打最大了,那么就将临界值threshold设置为最大的int值 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 根据新传入的newCapacity创建新Entry数组 Entry[] newTable = new Entry[newCapacity]; // 用来将原先table的元素全部移到newTable里面,重新计算hash,然后再重新根据hash分配位置 transfer(newTable, initHashSeedAsNeeded(newCapacity)); // 再将newTable赋值给table table = newTable; // 重新计算临界值,扩容公式在这儿(newCapacity * loadFactor) threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
HashMap
的线程不安全主要是发生在扩容函数中,即根源是在transfer函数中,JDK1.7中HashMap
的transfer
函数如下:
1 void transfer(Entry[] newTable, boolean rehash) { 2 int newCapacity = newTable.length; 3 for (Entry<K,V> e : table) { 4 while(null != e) { 5 Entry<K,V> next = e.next; 6 if (rehash) { 7 e.hash = null == e.key ? 0 : hash(e.key); 8 } 9 int i = indexFor(e.hash, newCapacity); 10 e.next = newTable[i]; 11 newTable[i] = e; 12 e = next; 13 } 14 } 15 }
这段代码是HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。理解了头插法后再继续往下看是如何造成死循环以及数据丢失的。
扩容造成死循环和数据丢失的分析过程
假设现在有两个线程A、B同时对下面这个HashMap进行扩容操作:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)