HashMap线程不安全,因扩容时rehash可能形成环形链表导致死循环、put操作非原子性引发数据覆盖、关键字段未用volatile修饰造成可见性问题。

Java中的HashMap不是线程安全的。多线程环境下,如果多个线程同时对同一个HashMap实例进行读写操作(尤其是put、remove等结构性修改),可能引发数据错乱、死循环、甚至程序卡死等问题。
为什么HashMap线程不安全?
核心原因在于其内部结构和操作缺乏同步保护,主要体现在以下几点:
- 扩容时的rehash可能引发环形链表:JDK 7中,多线程并发put触发扩容时,多个线程可能同时执行transfer()方法,因头插法+竞态条件,导致链表节点形成闭环;一旦后续get操作遍历该链表,就会无限循环(CPU 100%)。
- put操作非原子性:一次put包含计算hash、定位桶、检查是否已存在key、插入或替换value等多个步骤,中间状态对其他线程可见,且无锁保护,可能造成覆盖丢失(如两个线程同时put不同key到同一桶,其中一个被静默丢弃)。
- 成员变量未用volatile修饰:如table数组、size、threshold等关键字段未保证可见性与有序性,一个线程修改后,另一个线程可能读到过期值,导致判断逻辑错误(如误判容量未满而不触发扩容)。
哪些场景下容易暴露不安全?
不是所有多线程访问都会立刻出问题,但以下情况风险极高:
- 多个线程同时调用put()或remove()(尤其在初始容量小、频繁触发resize时)
- 一个线程正在扩容,另一个线程同时读或写同一桶位置
- 将HashMap作为共享缓存,未加任何外部同步就供多个服务线程使用
怎么保证线程安全?
根据实际需求选择合适方案,不盲目上锁:
立即学习“Java免费学习笔记(深入)”;
- 用ConcurrentHashMap替代:JDK 7采用分段锁(Segment),JDK 8改用CAS + synchronized锁单个Node,兼顾性能与安全,是首选方案。
- 用Collections.synchronizedMap()包装:底层给所有public方法加synchronized,简单但粒度粗(整个map一把锁),高并发下吞吐低。
- 手动加锁(如ReentrantLock或synchronized块):适用于复杂操作(如先查再put),但需确保所有访问路径都被保护,易遗漏。
- 只读场景用Collections.unmodifiableMap():若初始化后不再修改,转为不可变视图,天然线程安全。
基本上就这些。HashMap的设计目标本就是高性能单线程场景,线程安全从来不是它的职责——选对工具,比强行“修复”更重要。










