ConcurrentHashMap 的 get 要不要加锁?一次“多此一举”的心路历程

原文来自于:zha-ge.cn/java/37

ConcurrentHashMap 的 get 要不要加锁?一次"多此一举"的心路历程

搞 Java 的朋友,提起并发,哪个没和 Map 周旋过?我前阵子接了个项目,地图群魔乱舞,大家甩锅性能,一通群嘲都把目光怼到了 ConcurrentHashMap 上:有个人突然冒出个"get 其实也得加锁,不然不安全!"差点把我咖啡喷显示器上。行吧,这年头 Java 的黑魔法多,我就顺手扒一扒这坨事儿。

那天我为什么要想加锁

事情得从一次"有趣"的线上事故说起。某天我们线上业务偶有空指针,log 里指着 ConcurrentHashMap 的 get,键明明是 put 进去的呀。某位兄弟一拍脑袋:"可能是 get 也线程不安全吧,加个锁试试?"。 此言一出,气氛凝固了一秒------大家都用过单线程 HashMap 死锁的血泪教训,还真没好好想过 ConcurrentHashMap 的 get 是不是还能出妖风......于是我成了那个倒霉蛋,"你来扒下源码?"好嘞。

先说结论,ConcurrentHashMap 的 get,就是不用再套锁。 要/不要加锁的声音,不都基于"会不会读到脏数据"?但 get 怎么实现,真能跨线程出乱子吗?

源码刨根问底

我开了源码当夜宵。大致逻辑是:

  • get 是无锁(不是完全 lock free,不过查 table\[\] 懂的都懂)
  • get 查询,基本只读,不修改数据结构
  • put、remove 才会相关段加锁(Segment 锁)
  • 内部用 volatile 保证内存可见性,刚好防止"get 读到旧值"

来看个典型片段,get 方法蹭蹭两三行核心代码:

java 复制代码
Node<K,V> e = tabAt(tab, i);
while (e != null) {
    if (e.hash == hash && (e.key == key || (key != null && key.equals(e.key))))
        return e.val;
    e = e.next;
}

你看,这不就是读 table 读链表吗,压根不加锁,你想 lock 也没地儿加。 重点:tabAt 用的是 Unsafe 的 volatile 级别读,保证多线程下读数据时一定是新的,不会给你乱来。

当时看到这,我心想:锁你个头啊锁!

踩坑瞬间

不过说真的,在我没细扒前,心里真有点毛。

  • 看到线上偶发 null,我一度怀疑 get "撞枪口"读到 put/resize 的过程。
  • 还好源码里,table\[\] 扩容、节点插入都通过 volatile 顺序和 synchronized/Segment 锁兜底,只有 put 关心并发修正,get 始终只需保证可见性------并不是所谓"脏读"。
  • 再翻翻 JDK 8 的改动(从 Segment 分段到 CAS + volatile),发现设计就是读写分离,效率和安全都顾到了。

踩坑榜单:

  • 【玄学】以为加锁能防止 "偶发 null",其实根源是 put 过程或 get 时机出问题,锁甚至会徒增死锁风险。
  • 【过度设计】非要给 get 包一层锁,连类库作者都要摇头。

经验启示

回头一看,主流并发容器的坑基本一摸一样,总结几条赛博血泪经验:

  • ConcurrentHashMap 的 get,千万别再画蛇添足加锁,它天生为并发读优化;
  • 真遇到奇怪的 null,先查 put 过程是不是异常/覆盖/并发问题,不要甩锅给 get;
  • 记住 JDK 大佬们不是吃素的,遇到底层容器级别的"不是 bug 的 bug",十有八九是自己代码逻辑飘了------不要先"修锅",先修脑袋;
  • 要锁,也是操作复合场景(put-if-absent+double check 这种),不是普通 get;
  • 高并发基础设施,源码权威大于八卦文章,碰到疑点直接翻源码,比 YY 强多了。

写完这篇,喝口水冷静会,把 ConcurrentHashMap 的文档再温一遍。以后再有人说"要不要锁 get",直接甩上面两行源码,不费话。

行了,头发又少两根,溜了溜了~

相关推荐
xfan_me8 小时前
维修保养记录精准版 API 对接实战指南
java·大数据·python
Keven-zhou8 小时前
不用先学前端框架,Java后端也能独立交付项目?飞算JavaAI实测
java
海上小飞龙8 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
java·c++·python
Leo.yuan8 小时前
从“看全局“到“评成效“:央国企穿透式监管六步链路,哪些厂商能真正闭环
java·大数据·人工智能
小马哥程序开发8 小时前
[点赞收藏免费领取 · 项目源码]57105基于Spring Boot的充电桩管理系统的设计与实现
java·spring boot·源码·课程设计·毕设·大作业·课设
九皇叔叔9 小时前
【09】SpringBoot4 MyBatisPlus 增删改查(CRUD)
java·mybatis·mybatisplus
白杨尚青9 小时前
C++入门篇(十):string(上)——认识string:构造与三大遍历(一条龙讲透operator[]、迭代器、auto、范围for)
java·开发语言·c++·笔记·stl
anew___9 小时前
《从零手写操作系统 (11):文件系统初探——initrd与VFS抽象层》
java·javascript·算法
天天向上2919 小时前
Spring AI 实战:14 个踩坑记录,其中一个让我的测试全绿但产品是坏的
java·开源
正在走向自律10 小时前
从只写接口到独立交付前后端,飞算JavaAI能给Java后端工程师多少溢价?
java·springboot·ai编程·全栈开发·java求职·飞算javaai·金九银十