java hashmap 非线程安全,jdk7易因头插法导致环形链表和死循环,jdk8尾插法降低但未消除风险;推荐直接使用concurrenthashmap,或读写锁、单线程写等方案,禁用synchronizedmap等伪解决方案。

Java HashMap 本身不支持多线程安全写入,JDK 7 中因扩容采用头插法 + 多线程并发修改,极易形成环形链表,导致 get()、put() 等操作陷入无限循环,CPU 拉满却无明显 GC 或内存异常。JDK 8 虽改用尾插法并引入红黑树,但并未解决并发写入的线程安全性问题——死循环虽概率大幅降低,仍可能在极端并发扩容场景下发生(尤其配合自定义哈希或特殊负载)。真正可靠的解法是主动规避非线程安全的使用方式。
直接替换为 ConcurrentHashMap
这是最常用、最推荐的做法。ConcurrentHashMap 在 JDK 8+ 中采用分段锁(实际是基于 CAS + synchronized 锁单个桶)实现高并发读写,所有核心操作(put、get、computeIfAbsent 等)天然线程安全,且性能远高于全局加锁的 Collections.synchronizedMap()。
- 无需改动业务逻辑结构,只需把
HashMap<k> map = new HashMap();</k>替换为ConcurrentHashMap<k> map = new ConcurrentHashMap();</k> - 注意:它不保证复合操作的原子性(如“先检查再插入”需用
computeIfAbsent或putIfAbsent) - 迭代器弱一致性,不会抛
ConcurrentModificationException,但可能反映某次操作的中间状态
读多写少时加读写锁控制
若已有大量代码依赖 HashMap 接口,或需定制化行为(如特殊序列化、监听),可包裹一层显式同步机制。
- 用
ReentrantReadWriteLock:读操作用readLock(),允许多个线程并发读;写操作用writeLock(),独占排他 - 适用于读远多于写的场景,能比纯
synchronized提升吞吐量 - 避免在锁内执行耗时操作(如远程调用、IO),否则会阻塞其他线程
写操作集中串行化
当写入频率低、数据来源可控(如配置加载、定时刷新缓存),可将写操作收敛到单一线程或任务队列中执行。
- 例如用
ScheduledThreadPoolExecutor定期重建整个HashMap实例,对外提供 volatile 引用切换 - 或通过
BlockingQueue收集变更请求,由专用消费者线程批量应用到内部HashMap - 本质是用“不可变性 + 引用替换”替代并发修改,适合对实时性要求不苛刻的场景
绝对禁止的“伪解决方案”
这些做法看似简单,实则隐患严重,线上务必规避:
-
Collections.synchronizedMap(new HashMap()):仅包装了方法级synchronized,但put和get之间无原子性,迭代仍需手动同步,且锁粒度粗、性能差 - 只给
put加锁、忽略get或遍历:get虽不修改结构,但在扩容未完成时可能读到损坏链表,同样触发死循环 - 认为“流量小就不会出事”:死循环是竞态条件,和 QPS 无直接关系,一次偶发扩容冲突就可能让整台机器失联
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











