性能 ConcurrentHashmap 与 HashMap

Posted

技术标签:

【中文标题】性能 ConcurrentHashmap 与 HashMap【英文标题】:Performance ConcurrentHashmap vs HashMap 【发布时间】:2010-11-25 14:03:09 【问题描述】:

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

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

(我知道空值是不允许的)

更新

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

您在这里期待什么答案?

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

【讨论】:

问题已经澄清到没有并发访问的情况了。【参考方案3】:

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

【讨论】:

我认为当前的实现不会锁定 get(),但它们肯定会访问 volatile 变量。 是的,看源码:)【参考方案4】:

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

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

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

【讨论】:

问题已经澄清到没有并发访问的情况了。【参考方案5】:

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

现在回答你的问题:

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

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

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

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

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

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

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

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

【讨论】:

@Stu,一年多后,我发现了这篇文章,发现比尔的回答非常有帮助。无论 OP 是否足够感激接受答案,我仍然感谢比尔花时间写出来。 @Bill,谢谢! @littleFluffyKitty:很高兴听到。我的评论更多是针对@Mauli:P @BillMichell 谢谢!我刚刚发现了这一点,您的回答不仅使决定非常清晰,而且还教授了软件开发的一个重要方面——永远不要忘记其他人有一天会使用并可能编辑您的代码! 有趣的是,“鲍勃叔叔的”Clean Code 书中提到了这一点:the ConcurrentHashMap implementation performs better than HashMap in nearly all situations。没有提供任何统计数据来支持这一点,但我有兴趣看到这个经过验证... 我接受了另一个答案,因为它更符合我的要求,尽管我希望看到更多与实际数字的性能比较。【参考方案6】:

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

有趣的运行时差是here

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

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

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

【讨论】:

【参考方案7】:

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

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

代码可用on my 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

【讨论】:

很好的分析,但是 Collections.synchronizedCollection() 为每次读/写锁定对象,这不是 ConcurrentHashMap 的工作方式。所以我不会试图从你的统计数据中推断出任何关于 ConcurrentHashMap 与 HashMap 的性能(这是问题所要求的)。也许创建另一个问题:“Collections.synchronizedCollection() vs TreeMap”并在那里发布你的答案? @Atais 有什么软件可以画这个图? 您能描述一下您在图表上拥有的 4 个数据集吗?我不确定您是否将苹果与苹果进行比较。如果每个线程都有 HashMap 的副本,则应该与每个线程的 ConcurrentHashMap 副本进行比较(这有点意义)。如果您比较 mechanizm 以跨线程共享数据,那么您需要单个数据结构实例并比较不同的锁定 mechanizm。否则,您将无法工作的软件(由多个线程修改的 HashMap)与可以工作的软件(由多个线程修改的 ConcurrentHashMap)的性能进行比较。 基准测试似乎有缺陷,为什么get 图表显示它是 O(n)?它应该是 O(1),或者在绝对最坏的情况下(Java 8 之后)O(log(n))【参考方案8】:

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

【讨论】:

以上是关于性能 ConcurrentHashmap 与 HashMap的主要内容,如果未能解决你的问题,请参考以下文章

性能 ConcurrentHashmap 与 HashMap

ConcurrentHashMap原理分析

HashMap与ConcurrentHashMap

并发容器之ConcurrentHashMap

HashMap,ConcurrentHashMap 原理分析

ConcurrentHashMap线程安全,相对hashtable提高性能