Java ConcurrentHashMap中分区数量增加的缺点



Java ConcurrentHashMap在内部维护分区。每个分区都可以单独锁定。在某些情况下,多个线程访问的所有密钥都属于同一个分区,分区可能没有帮助。进一步增加分区的数量应该可以提高并发性。

为什么Java提供分区计数的默认值为16,而不是非常高的值?Map中有大量分区,性能会过热吗?

为什么Java提供分区计数的默认值为16,而不是非常高的值?

很少有这么多CPU(线程数量并不那么重要)同时使用相同的CHM。如果您真的需要这样做,通常有一种更好的方法来编写应用程序,从而避免这种情况。

例如,假设您有1000个线程,但只有8个CPU。这意味着最多只有8个线程将运行和访问CHM,假设您的程序没有做任何有用的事情,例如其他任何事情。

在实际程序中,很少有一个集合的使用时间超过10%。这是因为通常会涉及一些IO,或者重组线程以使用自己的集合副本并在最后将其收集在一起是有意义的,例如Map Reduce

Map中有大量分区,性能会过热吗?

你浪费了一点内存,这并不重要,但主要是浪费了一些一级缓存,这些缓存被限制为32KB,并且是相对宝贵的资源。

以下是javadoc所说的(Java 6):

"更新操作之间允许的并发性由可选的concurrentyLevel构造函数参数(默认值16)指导,用作内部大小调整的提示。该表在内部进行分区,以尝试在没有争用的情况下允许指定数量的并发更新。因为哈希表中的位置本质上是随机的,所以实际的并发性会有所不同。理想情况下,您应该选择一个值来容纳尽可能多的线程来同时修改表。使用比您需要的高得多的值可能会浪费空间和时间,而使用更低的值可能导致线程争用。但一个数量级内的高估和低估通常不会产生太大的影响。当已知只有一个线程将进行修改而所有其他线程将只进行读取时,值为1是合适的。此外,调整这个或任何其他类型的哈希表的大小是一个相对较慢的操作,因此,在可能的情况下,最好在构造函数中提供预期表大小的估计值。"

因此,简单的答案是,默认值(16)是限制并发和浪费空间之间的折衷。"非常高"的值会浪费大量空间。(正如Peter Lawrey所指出的,由于内存缓存效应,这可能会导致性能下降。)

另一件需要注意的事情是,LinkedHashMap实现将concurrencyLevel的值静默地上限为216。(至少,Java 6代码就是这么做的。)不过,很难想象在现实世界中会出现需要那么多并发的情况。

最新更新