HashMap 是不是线程安全的?如果让你来实现一个线程安全的 HashMap 你要怎么设计?如果不用加锁你要怎么设计?

Sherwin.Wei Lv7

HashMap 是不是线程安全的?如果让你来实现一个线程安全的 HashMap 你要怎么设计?如果不用加锁你要怎么设计?

回答重点

HashMap非线程安全 的。因为 HashMap 的内部实现并没有加锁,多个线程同时访问和修改时可能会引发数据竞争,导致数据不一致或陷入死循环等问题。

如何实现一个线程安全的 HashMap

要实现一个线程安全的 HashMap,有多种设计方案,下面是几种常见的实现方法:

使用 Collections.synchronizedMap

Java 提供了一个简单的方法,可以将非线程安全的 HashMap 包装为线程安全的版本:

1
Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

这种方法通过在 HashMap 的方法上加 synchronized 锁实现线程安全。不过,这种方式对整个 Map 加锁,会导致较高的锁竞争和性能开销,尤其是在高并发情况下。

参考ConcurrentHashMap 的分段锁

使用分段锁(在 Java 8 之后更改为 CAS + 分段桶机制)来实现线程安全,同时避免了整个 Map 加锁的问题。

  • 在 Java 7 中,ConcurrentHashMapMap 划分为多个 Segment,每个 Segment 是一个小的 HashMap,只有在同一个 Segment 上有冲突时才会加锁,减少了锁的粒度。
  • 在 Java 8 中,ConcurrentHashMap 引入了 CAS(Compare-And-Swap)操作和分段桶(Bucket)机制,大大减少了锁的使用,进一步提升了并发性能。

不使用锁的设计方案

如果要实现一个线程安全的 HashMap不使用锁,可以考虑以下几种方案:

CAS(Compare-And-Swap)+ 分段机制

可以借鉴 ConcurrentHashMap 的思想,使用分段机制结合 CAS 操作来减少锁的需求。以下是实现思路:

  1. 分段机制:将 Map 分成多个分段(如 16 个分段),每个分段是一个小的 HashMap。通过 key 的 hash 值定位到具体的分段,这样可以减少锁的粒度。
  2. CAS 操作:CAS 是一种无锁操作,可以避免传统锁带来的性能开销。CAS 操作检查某个变量是否等于期望值,如果是,则将其更新为新值;否则,返回失败。使用 CAS 操作可以在无锁的情况下实现线程安全的写入。
  3. 内部状态控制:对于需要扩容的操作,可以借助 CAS 和重试机制。比如扩容时,只对需要扩容的分段进行扩展,而不是整个 Map,以减少性能影响。

Copy-on-Write 机制

Copy-on-Write 是一种无锁实现的思路,适合读多写少的场景。实现思路如下:

  1. 每次写入操作(如 putremove)时,复制当前的 Map,在副本上进行修改,然后将副本替换为当前的 Map
  2. 读取时始终访问当前的 Map 实例,保证不会受到写入操作的影响。

这种方式的缺点是每次写入都需要复制整个 Map,会带来较大的内存和性能开销,所以只适合读多写少的场景。

扩展知识

Comments
On this page
HashMap 是不是线程安全的?如果让你来实现一个线程安全的 HashMap 你要怎么设计?如果不用加锁你要怎么设计?