
hashmap非线程安全,当一个线程执行get读取时,另一线程触发put导致扩容,可能引发链表环形化,造成get操作无限循环,结果不可预测且无任何保障。
hashmap非线程安全,当一个线程执行get读取时,另一线程触发put导致扩容,可能引发链表环形化,造成get操作无限循环,结果不可预测且无任何保障。
在Java中,HashMap 是典型的非线程安全集合类。其内部采用数组+链表(JDK 8起为红黑树)结构存储键值对,而扩容(resize) 是一个高危操作:当元素数量超过阈值(threshold = capacity × loadFactor)时,put 操作会触发扩容——新建两倍容量的数组,并将原桶中所有节点头插法(head insertion)重新散列到新数组中。
⚠️ 关键风险点在于:JDK 7 及更早版本中,多线程并发 resize 会导致链表反转成环形结构。例如,假设两个线程 T1 和 T2 同时进入 transfer() 方法(JDK 7 的扩容核心逻辑),因缺乏同步,它们可能交替修改同一链表节点的 next 引用,最终形成闭环:
// 简化示意:扩容中两个线程并发操作单向链表 A → B → null // 错误执行后可能变为 A → B → A → B → ...(环形链表)
此时,若另一线程调用 get(key),遍历该桶链表时将陷入死循环(e = e.next 永不为 null),CPU 占用飙升,程序假死——这不是偶发异常,而是明确可复现的未定义行为(undefined behavior)。
✅ 正确实践方案:
- ✅ 读多写少场景:使用
Collections.synchronizedMap(new HashMap())(全局锁,简单但吞吐低);- ✅ 高并发读写:首选
ConcurrentHashMap(JDK 8+ 基于CAS + synchronized 分段锁优化,支持并发读、安全扩容);- ❌ 禁止通过外部同步“临时修复”:如
synchronized(map) { map.get(...); }仅保护单次操作,无法规避 resize 过程中的内部竞态。
需要强调的是:“有时正常、有时死循环”不是bug概率问题,而是典型的数据竞争(data race)表现——JVM规范不保证其行为一致性,所谓“只在周二出错”正是对未定义行为的黑色幽默式描述。 生产环境必须杜绝 HashMap 的裸并发访问,将线程安全作为设计前提,而非事后补救。

















