HashMap 是线程安全的吗?多线程下会有什么问题?如何实现线程安全?

HashMap 不是线程安全的。
因为所有方法(如 putgetresize 等)都没有使用 synchronized 或 加锁 进行保护。这些操作都不是原子性的

在多线程环境下,使用 HashMap 可能会出现以下问题:

  • 扩容死循环:在 JDK 1.7 中,HashMap 使用头插法插入元素,当多个线程同时进行扩容操作时,可能会导致环形链表的形成,后续对同一哈希桶的get操作会陷入死循环。为了解决这个问题,在 JDK 1.8 中采用了尾插法插入元素,保持了链表元素的顺序,避免了死循环的问题。
  • 数据覆盖:当多个线程同时执行 put 操作时,如果它们计算出的索引位置相同,可能会造成前一个 key 被后一个 key 覆盖的情况,从而导致元素的丢失。

关于多线程安全的实现方案,可以采取以下措施:

  1. 如果并发要求不高或对遗留代码兼容

    • Hashtable 是 JDK 1.0 的遗留类,所有方法都用 synchronized 修饰,属于全表锁,(不允许 null 键值),性能较差,新代码中不建议使用

    • Collections.synchronizedMap 是 JDK 1.2 提供的包装类,同样采用全表锁(通过互斥对象),(允许 null 键值),但并发能力依旧有限,仅适合并发要求极低的场景。

  2. 如果是高并发场景,我通常会使用 ConcurrentHashMap。它针对并发做了专门优化:

    • 在 JDK 1.7 中采用分段锁(Segment),将数据分块,减少锁粒度,允许多个线程同时操作不同段。

    • 在 JDK 1.8 中优化为 CAS + synchronized 保证线程安全性,锁的粒度细化到数组的每个桶(Node),只有在操作同一个哈希桶时才会发生锁竞争,并发性能是更优。

总之,在多线程环境下,尽量不要直接使用 HashMap,而是根据并发量的高低选择合适的封装类或 ConcurrentHashMap

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐