性能并发哈希映射与哈希映射

2022-08-31 12:41:50

与HashMap相比,ConcurrentHashMap的性能如何,特别是.get()操作(我对只有几个项目的情况特别感兴趣,在0-5000之间)?

有什么理由不使用 ConcurrentHashMap 而不是 HashMap?

(我知道不允许空值)

更新

只是为了澄清,显然在实际并发访问的情况下性能会受到影响,但是在没有并发访问的情况下,性能如何比较?


答案 1

我真的很惊讶地发现这个话题如此古老,但还没有人提供有关此案的任何测试。使用 I 已经为 这两种情况创建了 的测试,并且用于两种情况:ScalaMeteraddgetremoveHashMapConcurrentHashMap

  1. 使用单线程
  2. 使用尽可能多的线程,因为我有可用的内核。请注意,由于HashMap不是线程安全的,我只是为每个线程创建了单独的HashMap,但使用了一个共享的 ConcurrentHashMap

代码在我的存储库中可用。

结果如下:

  • X 轴(大小)表示写入地图的元素数
  • Y 轴(值)以毫秒为单位显示时间

Add method Get method Remove method

总结

  • 如果要尽快对数据进行操作,请使用所有可用的线程。这似乎是显而易见的,每个线程都有1/n的全部工作要做。

  • 如果选择单线程访问使用,则速度会更快。对于方法,它的效率甚至高出3倍。只是速度更快,但并不多。HashMapaddgetConcurrentHashMap

  • 当使用多个线程进行操作时,与对每个线程进行单独操作同样有效。因此,无需将数据划分为不同的结构。ConcurrentHashMapHashMaps

总而言之,当您使用单线程时,ConcurrentHashMap的性能会更差,但是添加更多线程来完成工作肯定会加快该过程。

测试平台

AMD FX6100, 16GB Ram
Xubuntu 16.04, Oracle JDK 8 update 91, Scala 2.11.8


答案 2

线程安全性是一个复杂的问题。如果要使对象线程安全,请有意识地执行此操作,并记录该选择。使用你的类的人会感谢你,如果它在简化他们的使用时是线程安全的,但是如果一个曾经是线程安全的对象在将来的版本中变得不那么安全,他们会诅咒你。线程安全性虽然非常好,但不仅仅是圣诞节!

所以现在回答你的问题:

ConcurrentHashMap(至少在Sun的当前实现中)的工作原理是将底层映射划分为许多单独的桶。获取元素本身不需要任何锁定,但它确实使用原子/易失性操作,这意味着内存屏障(可能非常昂贵,并且会干扰其他可能的优化)。

即使在单线程情况下,JIT编译器可以消除原子操作的所有开销,仍然存在决定要查找哪个存储桶的开销 - 诚然,这是一个相对快速的计算,但是,它不可能消除。

至于决定使用哪个实现,选择可能很简单。

如果这是一个静态字段,你几乎肯定想要使用 ConcurrentHashMap,除非测试表明这是一个真正的性能杀手。您的类具有与该类的实例不同的线程安全期望。

如果这是一个局部变量,那么HashMap就足够了 - 除非你知道对对象的引用可能会泄漏到另一个线程。通过编码到 Map 接口,您可以在以后发现问题时轻松更改它。

如果这是一个实例字段,并且该类尚未设计为线程安全,则将其记录为非线程安全,并使用 HashMap。

如果您知道此实例字段是该类不是线程安全的唯一原因,并且愿意接受有承诺的线程安全所隐含的限制,请使用 ConcurrentHashMap,除非测试显示显著的性能影响。在这种情况下,您可以考虑允许类的用户以某种方式选择对象的线程安全版本,也许是通过使用不同的工厂方法。

在任何一种情况下,都应将类记录为线程安全(或有条件线程安全),以便使用类的人知道他们可以跨多个线程使用对象,并且编辑类的人知道他们将来必须维护线程安全。