HashMap中使用唯一键的多线程场景下是否具备线程安全性?
关于唯一键下HashMap线程安全性的解答
好问题!虽然你确保了每个线程操作的是唯一键,但很遗憾,这段代码依然不是线程安全的——问题出在HashMap的内部实现机制上,和键是否唯一无关。咱们来详细拆解原因,再给出可行的解决方案:
为什么即使键唯一,HashMap也不安全?
HashMap的put操作并不是一个原子性的操作,它包含了多个无同步保护的步骤:
- 计算键的哈希值
- 根据哈希值找到对应的桶(数组下标)
- 检查桶内是否存在重复键(你的场景下这步不会有冲突,但不影响后续问题)
- 插入新节点到桶中
- 判断当前容量是否达到阈值,触发扩容(如果需要)
多个线程同时执行这些步骤时,会引发两类问题:
- 内部结构损坏:比如两个线程同时触发扩容操作,可能导致HashMap的链表形成环形结构,后续的
get操作会陷入死循环;或者两个线程同时往同一个桶插入节点,导致其中一个节点被覆盖丢失。 - 内存可见性问题:一个线程插入的键值对,其他线程可能无法立即读取到最新状态——HashMap没有使用
volatile或同步机制来保证内存可见性,线程可能读取到缓存中的旧数据。
另外还要提一句:你原代码中的循环条件i > 6是错误的,这样循环一次都不会执行,线程根本不会启动,应该改成i <= 6。
如何修复这个问题?
有两种常见的解决方案,根据你的场景选择:
方案1:给HashMap的put操作加同步锁
如果坚持使用HashMap,可以用synchronized块来保护put操作,确保同一时间只有一个线程修改HashMap:
Map<Integer, String> myMap = new HashMap<>(); for (int i = 1 ; i <= 6 ; i++) { final int key = i; // 捕获循环变量时要注意,避免lambda引用可变变量的问题 new Thread(() -> { synchronized(myMap) { myMap.put(key, Integer.toString(key)); } }).start(); }
方案2:使用线程安全的ConcurrentHashMap
更推荐的方式是直接使用Java并发包提供的ConcurrentHashMap,它本身就为多线程场景设计,内部通过CAS+synchronized(JDK8+)实现高效的线程安全,不需要额外加锁:
Map<Integer, String> myMap = new ConcurrentHashMap<>(); for (int i = 1 ; i <= 6 ; i++) { final int key = i; new Thread(() -> { myMap.put(key, Integer.toString(key)); }).start(); }
总结
即使所有线程操作的是唯一键,HashMap的内部修改逻辑依然不是线程安全的,必须通过同步锁或者使用并发集合来保证线程安全。如果是多线程写入场景,优先选择ConcurrentHashMap,它的性能和安全性都更有保障。
内容的提问来源于stack exchange,提问作者Mor Blau
相关产品推荐
相关产品推荐

