news 2026/6/6 12:00:01

HashMap 在多线程的场景中会出现什么问题?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HashMap 在多线程的场景中会出现什么问题?

💡 核心结论:一句话先记住

HashMap 是个“单线程傲娇怪”,多线程一哄而上绝对会出事!老版本(JDK 1.7)多线程用它会直接让系统卡死(死循环),新版本(JDK 1.8)虽然不卡死了,但会悄悄丢数据(数据覆盖/全线程不安全)


🛑 两个版本的具体“翻车现场”

1. 老版本(JDK 1.7):致命的“死循环”(CPU 100%)

  • 怎么发生的:老版本的 HashMap 扩容时用的是“头插法”(新来的数据坐最前面)。如果有两个线程同时给它扩容,你推我抢之间,链表的指针就会被指反,最后自己咬住自己的尾巴,连成了一个环。
  • 后果:后面只要有人来找数据(执行get()),就会在这个环里无限绕圈、转到晕厥。表现出来就是服务器的 CPU 瞬间飙到 100%,整个服务直接瘫痪假死。

2. 新版本(JDK 1.8):低调的“数据覆盖”(悄悄丢数据)

  • 怎么发生的:官方在新版本换成了“尾插法”(乖乖去排队),虽然修复了死循环,但由于没有任何加锁保护,多线程并发put时仍然存在严重的数据碰撞覆盖和size 计数缺失问题。
  • 后果:连个报错都没有,数据丢得无影无踪,非常阴险。
💻 翻车现场源码级复现:

我们可以用一段简单的 Java 代码,亲眼看看 JDK 1.8 里 HashMap 是怎么悄悄丢数据的:

importjava.util.HashMap;importjava.util.Map;importjava.util.concurrent.CountDownLatch;publicclassHashMapRaceConditionDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{// 初始化一个普通的 HashMapfinalMap<String,String>unsafeMap=newHashMap<>();intthreadCount=2;intopsPerThread=10000;CountDownLatchlatch=newCountDownLatch(threadCount);// 线程 A:狂写数据ThreadthreadA=newThread(()->{for(inti=0;i<opsPerThread;i++){unsafeMap.put("ThreadA-"+i,"Value-"+i);}latch.countDown();});// 线程 B:也狂写数据ThreadthreadB=newThread(()->{for(inti=0;i<opsPerThread;i++){unsafeMap.put("ThreadB-"+i,"Value-"+i);}latch.countDown();});threadA.start();threadB.start();latch.await();// 等待两个线程都作妖结束// 理论上应该有 20000 条数据System.out.println("【理论期待大小】: "+(threadCount*opsPerThread));System.out.println("【实际 Map 大小】: "+unsafeMap.size());if(unsafeMap.size()<(threadCount*opsPerThread)){System.err.println("🚨 警告:数据对不上!发生了并发覆盖,部分数据悄悄蒸发了!");}}}

🛠️ 正确的替代方案(怎么抄作业?)

既然 HashMap 在并发场景这么不靠谱,我们该用谁?

1. ConcurrentHashMap(绝对首选 ⭐⭐⭐⭐⭐)

高并发战神。JDK 1.8 中它采用了Node 数组 + 链表 / 红黑树的结构,并利用CAS + synchronized进行了细粒度锁(只锁当前槽位/格子)的设计。既保证了绝对的安全,速度还飞快。

2.Collections.synchronizedMap(低并发备选 ⭐⭐)

相当于给普通的 HashMap 强行配了个粗鲁的保安,管你访问哪个格子,通通把整个 Map 锁住(对象锁),一次只放一个人进去,安全但排队很慢。

3. Hashtable(直接淘汰 ❌)

上个世纪的老古董,方法全加了synchronized,又慢又土,现代 Java 开发直接无视它。

💻 抄作业正确姿势代码:

将上面的高并发翻车代码,无缝切换为安全高效的ConcurrentHashMap

importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.CountDownLatch;publicclassConcurrentHashMapSafeDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{// ⭐ 唯一的区别:换成高并发战神 ConcurrentHashMapfinalMap<String,String>safeMap=newConcurrentHashMap<>();intthreadCount=2;intopsPerThread=10000;CountDownLatchlatch=newCountDownLatch(threadCount);ThreadthreadA=newThread(()->{for(inti=0;i<opsPerThread;i++){safeMap.put("ThreadA-"+i,"Value-"+i);}latch.countDown();});ThreadthreadB=newThread(()->{for(inti=0;i<opsPerThread;i++){safeMap.put("ThreadB-"+i,"Value-"+i);}latch.countDown();});threadA.start();threadB.start();latch.await();// 无论运行多少次,结果永远稳如老狗System.out.println("【理论期待大小】: "+(threadCount*opsPerThread));System.out.println("【实际 Map 大小】: "+safeMap.size());System.out.println("🛡️ 结果安全:一条数据都没丢!");}}

🎯 终极秒记口诀

1.7 头插扩容会反转,并发形成环形链,CPU 飙升服务挂!
1.8 尾插顺序不变了,死循环虽修复,数据覆盖仍存在!
解决方案:并发就用 ConcurrentHashMap,锁粒度细性能高!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 11:59:17

遗传算法实战:用Python解决N皇后问题

1. 项目概述&#xff1a;从理论到代码落地的遗传算法实战复盘你有没有试过&#xff0c;明明把遗传算法&#xff08;GA&#xff09;的“选择-交叉-变异”流程背得滚瓜烂熟&#xff0c;可一打开编辑器写代码&#xff0c;却卡在第一个问题上&#xff1a;怎么把一个“皇后摆法”变成…

作者头像 李华
网站建设 2026/6/6 11:58:43

利用快马ai快速搭建瑞芯微rv1106嵌入式linux开发原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 请基于瑞芯微rv1106芯片&#xff0c;生成一个嵌入式linux系统的基础项目框架&#xff0c;该项目需包含以下核心功能&#xff1a;第一&#xff0c;生成一个基于buildroot或yocto的定…

作者头像 李华
网站建设 2026/6/6 11:55:30

Allegro PCB设计实战:从封装管理到布线规则的常见问题与解决方案

1. 项目概述&#xff1a;一份来自一线的Allegro实战问题集最近在整理硬盘&#xff0c;翻出来一份十多年前刚接触Cadence Allegro时&#xff0c;自己记录的问题笔记。里面密密麻麻记满了各种稀奇古怪的报错、操作困惑和前辈们的解答。今天把它重新梳理、扩展&#xff0c;结合这些…

作者头像 李华
网站建设 2026/6/6 11:54:18

MTK设备终极刷机指南:3步解锁联发科芯片完整控制权

MTK设备终极刷机指南&#xff1a;3步解锁联发科芯片完整控制权 【免费下载链接】mtkclient MTK reverse engineering and flash tool 项目地址: https://gitcode.com/gh_mirrors/mt/mtkclient MTKClient是一款专为联发科芯片设备设计的开源刷机工具&#xff0c;提供从基…

作者头像 李华