08-并发 Map 的进化史:ConcurrentHashMap 怎么又安全又高效

发布时间:2026/10/16 9:15:56
08-并发 Map 的进化史:ConcurrentHashMap 怎么又安全又高效 CAS 锁桶头JDK8 把并发粒度细化到桶HashMap非线程安全、多线程会数据错乱。那并发场景下该用什么老一辈的答案是Hashtable或Collections.synchronizedMap但它们都用一把大锁高并发下吞吐惨不忍睹。ConcurrentHashMapCHM才是现代 Java 的标配。它经历了 JDK 7 的分段锁、JDK 8 的数组链表/树 CAS synchronized 锁单个桶头两次大重构。这一篇把它的演进和 JDK 8 实现讲透。一、前辈们的局限一把大锁Hashtable在方法上加synchronizedpublicsynchronizedVput(Kkey,Vvalue){...}publicsynchronizedVget(Objectkey){...}任何读写都抢同一把对象锁多线程下全是串行CPU 多核优势完全浪费。Collections.synchronizedMap同理只是把锁包在方法外。它们安全但慢在并发度高的缓存、计数场景直接成为瓶颈。二、JDK 7分段锁SegmentJDK 7 的 CHM 把数据分成 16 个Segment每个 Segment 是一把独立的 ReentrantLock内部各是一个小 HashMapConcurrentHashMap ├── Segment[0] (独立锁) → 小 HashMap ├── Segment[1] (独立锁) → 小 HashMap ├── ...默认 16 段 └── Segment[15]不同 Segment 的写操作互不阻塞理论上并发度 16。这比一把大锁进步很大但仍有问题① Segment 数固定扩不了并发度② 哈希冲突集中在一个 Segment 时该段仍是瓶颈③ 内存开销大每个 Segment 一套结构。JDK 8 干脆推倒重来。三、JDK 8更细的粒度锁单个桶头JDK 8 的 CHM 放弃了 Segment结构和 HashMap 几乎一致数组桶 链表/红黑树并发控制靠两把武器CAS无锁乐观尝试用于简单状态变更比如桶还是 null 时直接 CAS 放节点。synchronized 锁单个桶的头节点只在真正发生冲突、要改链表/树时才锁这一个桶其他桶完全不受影响。粒度从16 个段细化到每个桶并发度 ≈ 桶的数量远多于 16。四、put 流程CAS 试探 锁头节点简化版putVal逻辑finalVputVal(Kkey,Vvalue,booleanonlyIfAbsent){if(keynull||valuenull)thrownewNullPointerException();// CHM 不允许 nullinthashspread(key.hashCode());for(NodeK,V[]tabtable;;){NodeK,Vf;intn,i,fh;if(tabnull)initTable();// 懒初始化elseif((ftabAt(tab,i(n-1)hash))null){if(casTabAt(tab,i,null,newNode(hash,key,value)))break;// ① 桶为空CAS 无锁放入}elseif((fhf.hash)MOVED)tabhelpTransfer(tab,f);// 正在扩容协助搬迁else{synchronized(f){// ② 桶非空锁住这个桶的头节点if(tabAt(tab,i)f){if(fh0){/* 遍历链表相同 key 覆盖否则尾插 */...}elseif(finstanceofTreeBin)/* 走红黑树插入 */}}}if(sizethreshold)resize();// 计数用分段计数器低竞争}returnnull;}三个关键点桶空用 CAS最常见的首次放入完全无锁性能拉满。桶非空才 synchronized(f)只锁当前这个头节点其他桶并行无碍。锁的粒度极小。不允许 nullkey/value 为 null 直接抛 NPE——避免ConcurrentHashMap 里 null 到底表示’没这个 key’还是’value 就是 null’的歧义HashMap 单线程能区分并发下 get 返回 null 无法判断故干脆禁止。五、size 计数分段的 CounterCellHashMap用一个size字段CHM 多线程下直接会丢。JDK 8 用一个baseCountCounterCell[]的分段计数privatetransientvolatilelongbaseCount;privatetransientvolatileCounterCell[]counterCells;线程先 CAS 改baseCount竞争失败就落到某个CounterCell上各自累加最后size()把baseCount和所有CounterCell求和。相当于把一个热点计数器拆成多个避免所有线程抢同一变量。所以 CHM 的size()是弱一致的返回某一刻的近似快照不是实时精确值——高并发下追求精确 size 本身就没意义要用mappingCount()当估算。六、扩容多线程协助搬迁helpTransferHashMap 扩容是单线程搬家CHM 在扩容时允许其他线程一起搬某线程 put 时发现f.hash MOVED标识正在扩容的ForwardingNode就调用helpTransfer进去帮忙搬一部分桶搬完再继续自己的 put。用transferIndex把桶区间分给不同线程避免重复搬。配合高低位拆分同 HashMap把每个旧桶拆成 低桶/高桶 两条链表O(1) 重分配。这让 CHM 在扩容时仍能保持高吞吐不会因为一个人搬家、所有人等着而卡死。七、为什么读几乎无锁get全程几乎不加锁publicVget(Objectkey){NodeK,V[]tab;NodeK,Ve,p;intn,eh;Kek;inthspread(key.hashCode());if((tabtable)!null(ntab.length)0(etabAt(tab,(n-1)h))!null){if((ehe.hash)h((eke.key)key||(ek!nullkey.equals(ek))))returne.val;elseif(eh0)returne.find(h,key);// 树或 ForwardingNode 各自处理while((ee.next)!null)// 遍历链表if(e.hashh((eke.key)key||...))returne.val;}returnnull;}table和节点 value 用volatile修饰tabAt用Unsafe.getObjectVolatile读保证可见性。读操作不阻塞写、写操作的锁也很短所以读多写少场景 CHM 性能极好。这也是它取代 synchronizedMap 当缓存容器的根本原因。八、和 Hashtable / synchronizedMap 的对比维度HashtablesynchronizedMapConcurrentHashMap锁粒度整表一把锁整表一把锁桶头节点 / CAS读是否阻塞是方法锁是几乎无锁并发度11≈ 桶数允许 null否是否迭代器快速失败快速失败弱一致不抛 CME适用淘汰简单同步高并发首选CHM 的迭代器是弱一致的遍历时其他线程改了迭代器可能看到也可能看不到最新值但绝不抛ConcurrentModificationException可以边遍历边修改。九、常见误用map.putIfAbsent(k, v)不是检查创建的原子组合它本身就是原子的常用于单例缓存map.putIfAbsent(key, compute(key))——注意compute(key)仍然会先执行要不存在才计算请用computeIfAbsentJDK 8它只在 key 缺失时才调用函数避免无谓计算。get返回 null 不能区分无 key和value 为 nullCHM 直接禁止 null value别踩。size() 不精确高并发别依赖size()0做业务判断用isEmpty()也弱一致需要严格语义请加外部锁或用别的结构。十、面试追问QJDK 7 和 JDK 8 的 CHM 最大区别7 用 Segment 分段锁16 段、粒度粗、不可扩并发度8 去掉 Segment用 CAS synchronized 锁单个桶头粒度更细、并发度更高、内存更省。Q为什么 JDK 8 用 synchronized 而不是 ReentrantLock锁的是单个桶头节点、持有时间极短synchronized 在 JVM 里经过锁升级偏向→轻量→重量优化后这种极短临界区性能已不输 ReentrantLock还省了 AQS 的对象开销。而且锁的粒度已经足够小没必要上更复杂的锁。QCHM 能完全替代 HashMap 吗单线程优先 HashMap更轻、允许 null只有并发才上 CHM。别为了显得高级在无并发处用 CHM反而多了 volatile/CAS 开销。总结ConcurrentHashMap 用CAS 无锁试探 synchronized 锁单个桶头把并发粒度细化到桶读几乎无锁、写互不阻塞分段 CounterCell 做弱一致计数多线程协助扩容避免搬家卡顿JDK 8 相比 JDK 7 的 Segment 分段锁粒度更细、并发更高、内存更省。它不允许 null、迭代弱一致是并发键值存储的现代标配彻底淘汰了 Hashtable 和 synchronizedMap。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询