性能ConcurrentHashmap vs HashMap

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

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

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

更新

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

我真的很惊讶,发现这个话题是如此之久,但还没有人提供任何关于这个案件的testing。 使用ScalaMeter我已经在两种情况下创build了对HashMapConcurrentHashMapaddgetremovetesting:

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

代码可在我的回购 。

结果如下:

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

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

摘要

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

  • 如果您select单线程访问使用HashMap ,它只是更快。 add方法甚至可以提高3倍的效率。 只有在ConcurrentHashMapget的速度更快,但并不多。

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

综上所述,当你使用单线程的时候, ConcurrentHashMap的性能会变差,但是增加更多的线程来完成这个工作肯定会加速这个过程。

testing平台

AMD FX6100,16GB RAM
Xubuntu 16.04,Oracle JDK 8更新91,Scala 2.11.8

线程安全是一个复杂的问题。 如果你想使一个对象线程安全,有意识地做,并logging该select。 使用你的类的人会感谢你,如果它是简单的线程安全的使用,但他们会诅咒你,如果一个曾经线程安全的对象变得不是在未来的版本。 线程安全,虽然真的很好,不只是圣诞节!

所以,现在你的问题:

ConcurrentHashMap(至less在Sun当前的实现中 )通过将底层映射分成多个单独的桶来工作。 获取元素本身并不需要任何locking,但它确实使用primefaces/易失性操作,这意味着内存屏障(可能非常昂贵,并妨碍其他可能的优化)。

即使所有的primefaces操作的开销都可以通过JIT编译器在单线程的情况下被消除,但是仍然存在决定要查看哪个桶的开销 – 虽然这是一个相对较快的计算,但是它仍然是不可能消除。

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

如果这是一个静态字段,那么几乎肯定会使用ConcurrentHashMap,除非testing显示这是一个真正的性能杀手。 你的class级对这个class级有不同的线程安全期望。

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

如果这是一个实例字段,并且该类未被devise为线程安全的,则将其logging为不是线程安全的,并使用HashMap。

如果您知道此实例字段是类不是线程安全的唯一原因,并且愿意忍受有前途的线程安全隐含的限制,则使用ConcurrentHashMap,除非testing显示重大的性能影响。 在这种情况下,您可能会考虑允许类的用户以某种方式select对象的线程安全版本,也许可以使用不同的工厂方法。

在任何一种情况下,都要将该类logging为线程安全的(或者有条件的线程安全的),以便使用类的人知道他们可以跨多个线程使用对象,而编辑类的人知道他们将来必须保持线程安全。

我build议你测量它,因为(出于某种原因) 可能存在一些依赖于你正在存储的特定对象的哈希分布。

标准哈希映射不提供并发保护,而并发哈希映射提供。 在可用之前,你可以包装hashmap来获得线程安全访问权限,但这是粗粒度locking,意味着所有并发访问都被序列化,这可能会影响性能。

并发哈希映射使用locking剥离并仅locking受特定locking影响的项目。 如果你正在运行一个像热点这样的现代虚拟机,那么虚拟机将尽可能地使用locking偏移,粗体和椭圆,所以你只有在实际需要的时候才会支付锁的代价。

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

在1000个元素的散列表的情况下,对于整个表,使用10个锁保存接近10000个线程插入的时间的一半,10000个线程正在从中删除。

有趣的运行时间差在这里

始终使用并发数据结构。 除了条形码的缺点(下面提到)变成频繁的操作。 在这种情况下,你将不得不获得所有的锁? 我读过,最好的方法是recursion。

如果有一种方法可以在不影响数据完整性的情况下将高争用锁分解为多个锁,则locking条带是非常有用的。 如果这是可能的,应该采取一些思路,并不总是如此。 数据结构也是决定的因素。 所以如果我们使用一个大数组来实现一个哈希表,那么使用整个哈希表的一个锁来同步它将导致线程顺序地访问数据结构。 如果这是散列表上的相同位置,那么它是必要的,但是,如果他们正在访问表的两个极端。

锁条的下行是很难得到受条带影响的数据结构的状态。 在这个例子中,表的大小,或者试图列出/枚举整个表可能是麻烦的,因为我们需要获取所有的条形锁。

你在这里期待什么答案?

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

目前还不清楚你的意思。 如果你需要线程安全,你几乎没有select – 只有ConcurrentHashMap。 在get()调用中,它肯定会有性能/内存损失 – 访问volatilevariables,如果不幸运,则会locking。