听说过 HashMap 多线程的血泪史吗?
原文来自于: https://zha-ge.cn/java/47
听说过 HashMap 多线程的血泪史吗?
还记得刚学 Java 的时候,心里总觉得 HashMap 好用得不行,插入、查找都飞快,容量伸缩还能自动安排。结果——等我真把它拉到多线程场景,差点跪着给老板写道歉信。
说出来你可能不信,我就是那种天真地以为“反正 Java 自带的,肯定线程安全”的理工直男。下面,开聊!
若有人问多线程加速,HashMap 你敢用吗?
场景是这样的:业务量上来了,老板说多开线程嘛,大家一起 put,效率肯定高!
我一想对啊,把 HashMap 搬出来,N 个线程协作,结果有一天线上报警——
- 什么 key 居然找不到了!
- 某些 value 被莫名其妙覆盖。
- 更绝的是,偶尔居然死循环,CPU 飙飞天。
线上问题现场仿佛 CSI——一堆未解之谜。
踩坑瞬间
来个(痛苦)回忆杀:
比如有一段类似的代码:
map.put(key, value);
// 省略了一堆同步保护
看起来平平无奇,但只要多个线程同时执行上面的操作,HashMap 内部的数据结构就会乱炸!
- 有时 key 会丢失。
- 有些槽位数据会混乱。
- 最可乐的:JDK 8 以前,rehash 那一刻,链表可能自我拼接成了环……嗯?线性结构成了环形大冒险?查找 key 时 map.get() 直接死循环,主线程卡死!
那种“程序假死但不报错”的恐怖你体会过吗?就像地狱笑话:老板问你怎么查日志,你只能说“emmm……卡住了,日志没打印”。
当年我是怎么醒悟的
首先,翻官方文档——HashMap 明明脸都写着“非线程安全”,Javadoc 早就友情提示。只是我太年轻了!
然后追源码,才发现 put 操作其实是:
- 新建 Entry 节点;
- 塞到对应 bucket;
- 如果碰上扩容、rehash……线程打架时可能你拿着旧指针,他拽着新数据,谁都不认谁;
- 链表如何并发插入?老大难!(见鬼的环,真能恶心死人)
解决吗?有办法。
多线程下的救命稻草
- 要么用
ConcurrentHashMap,天生好斗分段锁,线程见面也能好好说话了; - 或者你整个
Collections.synchronizedMap(oldHashMap),自己在外面加大锁; - 再不济,自己写
synchronized块,然而锁得死死的,性能哭晕厕所。
直接贴个代码镇楼:
Map<K,V> syncMap = Collections.synchronizedMap(new HashMap<>());
// 或者直接上 ConcurrentHashMap
Map<K,V> concurrentMap = new ConcurrentHashMap<>();
经验启示
吐血整理踩坑经验,愿后来人少写几行加班代码:
- HashMap 本身绝不适合多线程并发操作。
- 多线程推荐:
ConcurrentHashMap(标配);Collections.synchronizedMap(效率低下,慎用);- 显式同步(写起来丑,慎用)。
- 死循环、数据丢失之类的锅 HashMap 背的起,但你得长点心。
- 多看 Javadoc!多看源码!千万别想当然。
- 日常实在太苦——记得多喝水,少加班😏。
写在最后
HashMap 这个家伙,单线程下妥妥好用,一上多线程就暴躁成渣。后来我每次用到它,就像见到一位不靠谱的前任,眉头一皱总得加点 Concurrent。
程序员的成长,都是这些血泪踩坑换来的。发文纪念那年那月被环形链表支配的恐惧。
各位看官,别重蹈我的覆辙哈!下次遇到“线程安全”,别忘了多留个心眼。
——码字收尾的程序猿
共同学习,写下你的评论
评论加载中...
作者其他优质文章