繁体   English   中英

性能 ConcurrentHashmap 与 HashMap

[英]Performance ConcurrentHashmap vs HashMap

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

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

(我知道不允许使用空值)

更新

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

我真的很惊讶这个话题这么老,但还没有人提供任何关于这个案例的测试。 使用ScalaMeter ,我在两种情况下为HashMapConcurrentHashMap创建了addgetremove的测试:

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

代码在我的 repo 上可用。

结果如下:

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

添加方法 获取方法 删除方法

摘要

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

  • 如果您选择单线程访问使用HashMap ,它会更快。 对于add方法,它的效率甚至提高了 3 倍。 只有getConcurrentHashMap上更快,但并不多。

  • 在具有多个线程的ConcurrentHashMap上操作时,它与为每个线程在单独的HashMaps上操作同样有效。 因此无需将数据划分为不同的结构。

综上所述,单线程时ConcurrentHashMap的性能较差,但添加更多线程来完成工作肯定会加快进程。

测试平台

AMD FX6100、16GB 内存
Xubuntu 16.04、Oracle JDK 8 更新 91、Scala 2.11.8

线程安全是一个复杂的问题。 如果你想让一个对象线程安全,有意识地去做,并记录那个选择。 使用你的类的人会感谢你,如果它是线程安全的,因为它简化了他们的使用,但如果一个曾经是线程安全的对象在未来的版本中变得不安全,他们会诅咒你。 线程安全虽然非常好,但不仅仅适用于圣诞节!

所以现在回答你的问题:

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

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

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

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

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

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

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

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

我建议您对其进行测量,因为(出于一个原因)可能对您存储的特定对象的散列分布有一定的依赖性。

标准哈希图不提供并发保护,而并发哈希图提供。 在它可用之前,您可以包装 hashmap 以获得线程安全访问,但这是粗粒度锁定,意味着所有并发访问都被序列化,这确实会影响性能。

并发散列图使用锁剥离并且只锁定受特定锁影响的项目。 如果您在现代虚拟机(如热点)上运行,虚拟机将尽可能尝试使用锁偏置、粗化和省略,因此您只需在实际需要时为锁支付罚款。

总之,如果您的地图将被并发线程访问并且您需要保证其状态的一致视图,请使用并发哈希图。

在 1000 个元素的哈希表的情况下,对整个表使用 10 个锁可以节省将近一半的时间,当 10000 个线程插入和 10000 个线程从中删除时。

有趣的运行时差异在这里

始终使用并发数据结构。 除非条带化的缺点(下面提到)成为一个频繁的操作。 在那种情况下,您将必须获得所有锁? 我读到最好的方法是递归。

当有一种方法可以在不损害数据完整性的情况下将高争用锁分解为多个锁时,锁条带化很有用。 如果这可能或不可能,应该考虑一下,但情况并非总是如此。 数据结构也是影响决策的因素。 所以如果我们使用一个大数组来实现一个哈希表,对整个哈希表使用一个锁来同步它会导致线程顺序访问数据结构。 如果这是哈希表上的相同位置,那么这是必要的,但是,如果他们正在访问表的两个极端怎么办。

锁条带化的缺点是很难获得受条带化影响的数据结构的状态。 在示例中,表的大小或尝试列出/枚举整个表可能很麻烦,因为我们需要获取所有条带锁。

你在这里期待什么答案?

显然取决于与写入同时发生的读取次数以及法线贴图必须在您的应用程序中的写入操作中“锁定”多长时间(以及您是否会在ConcurrentMap上使用putIfAbsent方法) . 任何基准在很大程度上都将毫无意义。

不清楚你的意思。 如果你需要线程安全,你几乎别无选择——只有 ConcurrentHashMap。 而且它在 get() 调用中肯定会有性能/内存损失 - 如果你不走运,访问 volatile 变量和锁定。

当然,没有任何锁定系统的 Map 胜过具有需要更多工作的线程安全行为的 Map。 Concurrent 的要点是在不使用同步的情况下是线程安全的,因此比 HashTable 更快。 对于 ConcurrentHashMap 与 Hashtable(同步),相同的图形将非常有趣。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM