当前位置: 首页 > news >正文

ConcurrentHashMap 底层原理:面试必问的并发安全容器

我秋招面试字节的时候,被问到这么一道题:

“HashMap 线程不安全,那你在多线程场景下用什么?”

我脱口而出:“ConcurrentHashMap。”

面试官追问:“那你说说它是怎么保证线程安全的?”

我卡壳了。

说出来不怕你笑话,我当时只会背"分段锁"三个字,但具体怎么实现的、为什么 JDK 1.8 要改成 CAS + synchronized、一致性是怎么保证的——全是一团浆糊。

这篇文章,就是我踩过坑之后写给自己的复盘笔记。


一、为什么 HashMap 不安全?

在说 ConcurrentHashMap 之前,先搞清楚 HashMap 为什么不安全。

三个字:并发修改

两个线程同时往 HashMap 里 put 数据,可能发生:

  1. 数据覆盖:线程 A 和 B 同时算出了相同的 index,A 写入后 B 直接覆盖
  2. 死循环:JDK 1.7 的扩容 rehash,链表可能形成环,导致死循环(这个坑我在线上踩过)
  3. 数组越界:扩容时数组引用不一致,导致空指针

所以多线程下用 HashMap,约等于给自己埋雷。


二、JDK 1.7:分段锁 SegmentedMap

怎么实现的?

// JDK 1.7 的结构Segment[]segments;// 每个 Segment 是一把锁// 一次 putpublicVput(Kkey,Vvalue){inthash=hash(key);intsegIndex=hash%segments.length;synchronized(segments[segIndex]){// 只锁一个 Segment// 在 Segment 内部操作 HashMap}}

核心思想:把整个 map 分成 N 个段(默认 16),每个段独立加锁。

优点

  • 多个线程可以并发操作不同的段,吞吐量提升

缺点

  • 锁的粒度还是太粗(锁的是整个 Segment)
  • 并发度受 Segment 数量限制,16 就是天花板

三、JDK 1.8:CAS + Synchronized

为什么改?

JDK 1.7 的分段锁听起来不错,但问题来了:

  • Segment 数量固定,并发度上不去
  • 实现复杂,代码维护成本高

于是 JDK 1.8 直接抄了 HashMap 的数据结构,改用桶级锁 + CAS

核心机制

// JDK 1.8 的结构Node[]table;// 和 HashMap 一样,数组 + 链表/红黑树// 一次 put 的大致逻辑1.计算 hash,找到数组位置2.if(该位置为空){// 用 CAS 写入,可能失败重试CAS+自旋写入}else{// 该位置有数据,加锁synchronized(该位置的Node){// 链表/红黑树插入}}

两个核心武器:

  1. CAS:乐观锁,适合低并发场景,线程不阻塞
  2. synchronized:悲观锁,适合高并发写入,锁单个桶

什么时候用哪个?

场景用什么原因
数组位置为空CAS无竞争,直接写入
链表/红黑树操作synchronized需要独占修改

四、面试高频问题

Q1:ConcurrentHashMap 怎么保证线程安全的?

三板斧:

  • CAS 保证原子性:空位置写入
  • synchronized 保证可见性:链表/红黑树操作
  • volatile 保证可见性:Node 的 value 和 next 指针

Q2:和 Hashtable 有什么区别?

对比项HashtableConcurrentHashMap
锁粒度全局一把锁桶级锁(JDK 1.8)
并发度低(等于1)高(N 个桶)
实现复杂度简单复杂
推荐不推荐推荐

我的踩坑:以前觉得 Hashtable 更"安全",结果生产环境用了之后 QPS 腰斩。换成 ConcurrentHashMap 之后,吞吐量直接翻倍。

Q3:JDK 1.8 为什么用 synchronized 而不是 ReentrantLock?

官方的解释:

  • JVM 层面优化:synchronized 在 JVM 层面做了很多优化(偏向锁、轻量级锁、自旋锁)
  • API 简化:不需要手动释放锁,不容易出错
  • 性能足够:在 JVM 的优化下,synchronized 性能已经不输 ReentrantLock

Q4:size() 怎么保证准确的?

不能保证准确。

ConcurrentHashMap 的 size() 是一个历史遗留问题:

  • JDK 1.7:每个 Segment 独立计数,累加时需要加锁
  • JDK 1.8:用一个 volatile 的 baseCount + CounterCell 数组,不保证完全准确

为什么不用锁保护?因为要保证性能,牺牲了一点准确性。


五、我踩过的坑

坑1:以为 size() 是准确的

// 错误用法if(map.size()>0){// 以为一定不为空,结果翻车}// 正确用法if(!map.isEmpty()){// 用 isEmpty() 判断,更可靠}

坑2:putIfAbsent 的误用

// 场景:实现分布式锁map.putIfAbsent(key,value);// 以为这样就能保证只有一个线程成功// 实际上:需要检查返回值判断是否成功StringoldValue=map.putIfAbsent(key,value);if(oldValue!=null){// 说明已经有值了,获取锁失败}

坑3:遍历时修改

// 错误:ConcurrentHashMap 的迭代器是弱一致性的for(Stringkey:map.keySet()){if(需要删除){map.remove(key);// 可能抛异常}}// 正确:用迭代器的 removeIterator<String>iter=map.keySet().iterator();while(iter.hasNext()){Stringkey=iter.next();if(需要删除){iter.remove();// 安全删除}}

六、记忆口诀

ConcurrentHashMap 三层保障: 1. CAS 写入(空位置,原子操作) 2. synchronized 修改(链表/红黑树,独占锁) 3. volatile 可见(Node.value 和 next) 对比 Hashtable: - Hashtable = 全局锁 = 串行 - CHM = 桶锁 = 并行

写在最后

面试问到 ConcurrentHashMap,其实是在考你对并发的理解。

不要只背"分段锁"、"CAS + synchronized"这几个词。要能说出来:

  • 什么场景用 CAS,什么场景用 synchronized
  • 为什么这样设计
  • 有什么取舍

我被问住那次,就是因为只背了概念,没理解为什么。

后来我花了一整晚看源码,把 JDK 1.7 和 1.8 的区别、put/get/remove 的流程都手画了一遍。

技术这东西,真的不能只背,要理解本质。

http://www.jsqmd.com/news/686372/

相关文章:

  • GBase 8a数据库双活容灾方案之被动灾备切换简介
  • 终极指南:3分钟让小爱音箱变身AI智能语音助手
  • 2026年聊聊马鞍山安防监控安装实力机构 - myqiye
  • 携程任我行礼品卡能变现吗?教你轻松实现价值最大化 - 团团收购物卡回收
  • IDE Eval Resetter:JetBrains试用期无限重置终极指南
  • Windows Cleaner实战指南:三步解决C盘爆红难题,释放宝贵系统空间
  • cf2225D
  • GBase数据库常用名词解释(之一)
  • Debian11最小安装避坑指南:从镜像下载到SSH配置全流程
  • PotPlayer字幕翻译终极指南:百度翻译插件完整使用教程
  • 2026年鸭屎香茶叶定制厂家哪家好,五山茶叶不容错过 - 工业品网
  • 如何快速解决C盘爆红问题:Windows Cleaner完整使用指南
  • 探讨南京、芜湖等地好用的LED显示屏安装品牌推荐 - mypinpai
  • zteOnu工具实战:5分钟解锁中兴光猫工厂模式获取完整控制权
  • Qwen3-4B-Instruct完整指南:支持PDF/EPUB/Markdown长文档问答系统搭建
  • 老芯片新玩法:ICL8038信号发生器的现代化改造与扩展应用思路
  • 聊聊知名的监控安装企业,南京地区靠谱的有哪些 - 工业品网
  • 微信聊天记录永久保存指南:开源工具WeChatExporter完整备份方案
  • 如何高效重置JetBrains IDE试用期:5个简单步骤告别30天限制
  • 深入分析广东鸭屎香茶叶厂家,五山茶叶口碑出众哪家强 - 工业品牌热点
  • Tomato-Novel-Downloader:一站式番茄小说下载与格式转换解决方案
  • nli-MiniLM2-L6-H768效果展示:英文新闻事件报道与时间线陈述中立性验证
  • Phi-3.5-Mini-Instruct地理信息:空间分析问答+GIS操作提示+制图规范生成
  • 从Kaggle到GitHub:手把手教你用Colab打造云端AI开发流水线
  • IDE Eval Resetter:JetBrains IDE试用期智能管理开源方案的技术实现
  • Windows右键菜单管理终极指南:如何快速清理和自定义你的右键菜单
  • 深度分析鲜目录寿司加盟行业,浙江地区加盟哪个品牌靠谱? - 工业推荐榜
  • 通用GUI编程技术——图形渲染实战(三十七)——D3D11初始化与SwapChain:从零搭建GPU渲染框架
  • 避障小车DIY实战:用STM32F103C8T6和HC-SR04实现自动避障(附完整代码)
  • GBase 8c多模态分布式数据库核心架构详解