Java ConcurrentHashMap最佳实践

ConcurrentHashMap与HashMap类非常相似,不同之处在于ConcurrentHashMap提供内部维护的并发性。这意味着在多线程应用程序中访问ConcurrentHashMap时不需要同步块。

//Initialize ConcurrentHashMap instance
ConcurrentHashMap<String, Integer> m = new ConcurrentHashMap<String, Integer>();

//Print all values stored in ConcurrentHashMap instance
for each (Entry<String, Integer> e : m.entrySet())
{
system.out.println(e.getKey()+"="+e.getValue());
}

上面的代码在您的应用程序的多线程环境中是合理有效的。我说“合理有效”的原因是,以上代码还提供了线程安全性,但仍然会降低应用程序的性能。并引入了ConcurrentHashMap来提高性能,同时确保线程安全,对吗?

那么,我们在这里缺少什么呢?

要了解这一点,我们需要了解ConcurrentHashMap类的内部工作。最好的开始方法是查看构造函数参数。ConcurrentHashMap的完全参数化的构造函数采用3个参数,initialCapacity,loadFactor和concurrencyLevel。

1)initialCapacity
2)loadFactor
3)concurrencyLevel

前两个名称很简单,但最后一个很棘手。这表示分片的数量。它用于在内部将ConcurrentHashMap划分为该数量的分区,并创建相等数量的线程以将线程安全性维持在分片级别。

并发哈希图
并发哈希图

 

“ concurrencyLevel”的默认值为16。这意味着每当我们使用默认构造函数创建ConcurrentHashMap的实例时,甚至添加第一个键-值对之前,这将有16个分。这也意味着为各种内部类(例如ConcurrentHashMap $ Segment,ConcurrentHashMap $ HashEntry []和ReentrantLock $ NonfairSync)创建实例。

在大多数情况下,在正常应用中,单个分片能够以合理数量的键值对处理多个线程。而且性能也将是最佳的。具有多个分片只会使内部事情变得复杂,并会引入许多不必要的对象来进行垃圾回收,而所有这些都不会提高性能。

使用默认构造函数为每个并发哈希图创建的额外对象的比例通常为1到50,即对于100个这样的ConcurrentHashMap实例,将创建5000个额外对象。

基于以上内容,我建议您明智地使用构造函数参数,以减少不必要的对象数量并提高性能

一个好的方法是进行如下初始化:

ConcurrentHashMap<String, Integer> instance = new ConcurrentHashMap<String, Integer>(16, 0.9f, 1);

初始容量为16,可确保在调整大小之前具有相当数量的元素。加载因子0.9确保ConcurrentHashMap内部的密集包装,这将优化内存使用。并将concurrencyLevel设置为1将确保仅创建和维护一个分片。

请注意,如果您正在处理非常高的并发应用程序,并且在ConcurrentHashMap中更新的频率很高,则应考虑将concurrencyLevel增加到大于1,但同样,它应该是计算正确的数字,以获得最佳结果。

学习愉快!

saigon has written 1440 articles

Leave a Reply