风影游侠
184.77MB · 2025-11-27
原文来自于:zha-ge.cn/java/37
搞 Java 的朋友,提起并发,哪个没和 Map 周旋过?我前阵子接了个项目,地图群魔乱舞,大家甩锅性能,一通群嘲都把目光怼到了 ConcurrentHashMap 上:有个人突然冒出个“get 其实也得加锁,不然不安全!”差点把我咖啡喷显示器上。行吧,这年头 Java 的黑魔法多,我就顺手扒一扒这坨事儿。
事情得从一次“有趣”的线上事故说起。某天我们线上业务偶有空指针,log 里指着 ConcurrentHashMap 的 get,键明明是 put 进去的呀。某位兄弟一拍脑袋:“可能是 get 也线程不安全吧,加个锁试试?”。 此言一出,气氛凝固了一秒——大家都用过单线程 HashMap 死锁的血泪教训,还真没好好想过 ConcurrentHashMap 的 get 是不是还能出妖风……于是我成了那个倒霉蛋,“你来扒下源码?”好嘞。
先说结论,ConcurrentHashMap 的 get,就是不用再套锁。 要/不要加锁的声音,不都基于“会不会读到脏数据”?但 get 怎么实现,真能跨线程出乱子吗?
我开了源码当夜宵。大致逻辑是:
来看个典型片段,get 方法蹭蹭两三行核心代码:
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 级别读,保证多线程下读数据时一定是新的,不会给你乱来。
当时看到这,我心想:锁你个头啊锁!
不过说真的,在我没细扒前,心里真有点毛。
踩坑榜单:
回头一看,主流并发容器的坑基本一摸一样,总结几条赛博血泪经验:
写完这篇,喝口水冷静会,把 ConcurrentHashMap 的文档再温一遍。以后再有人说“要不要锁 get”,直接甩上面两行源码,不费话。
行了,头发又少两根,溜了溜了~