我有一个Map,它将被多个线程同时修改 .
Java API中似乎有三种不同的同步Map实现:
-
Hashtable
-
Collections.synchronizedMap(Map)
-
ConcurrentHashMap
据我所知, Hashtable
是一个旧的实现(扩展过时的 Dictionary
类),后来经过调整以适应 Map
接口 . 虽然它是同步的,但似乎有严重的scalability issues并且不鼓励新项目 .
但是其他两个怎么样? Collections.synchronizedMap(Map)
和 ConcurrentHashMap
返回的 Map 之间有什么区别?哪一种适合哪种情况?
18 回答
Hashtable和ConcurrentHashMap不允许
null
键或null
值 .Collections.synchronizedMap(Map)同步 all 次操作(
get
,put
,size
等) .ConcurrentHashMap支持检索的完全并发,并且可以调整更新的预期并发性 .
像往常一样,涉及并发 - 开销 - 速度权衡 . 您确实需要考虑应用程序的详细并发要求来做出决策,然后测试代码以确定它是否足够好 .
我们可以使用ConcurrentHashMap和synchronisedHashmap和Hashtable来实现线程安全 . 但是如果你看一下他们的架构,会有很多不同 .
Synchronized Map:
同步映射与Hashtable也没有太大差别,并且在并发Java程序中提供类似的性能 . Hashtable和SynchronizedMap之间的唯一区别是SynchronizedMap不是遗留物,您可以使用Collections.synchronizedMap()方法包装任何Map以创建它的同步版本 .
ConcurrentHashMap:
ConcurrentHashMap类提供标准HashMap的并发版本 . 这是对Collections类中提供的synchronizedMap功能的改进 .
与Hashtable和Synchronized Map不同,它永远不会锁定整个Map,而是将段划分为段,并对其进行锁定 . 如果读取器线程的数量大于写入器线程的数量,则它的性能会更好 .
默认情况下,ConcurrentHashMap分为16个区域并应用锁定 . 初始化ConcurrentHashMap实例时,可以设置此默认编号 . 在特定段中设置数据时,将获取该段的锁定 . 这意味着如果两个更新各自影响单独的存储桶,则它们仍可以同时安全地执行,从而最大限度地减少锁争用并最大限度地提高性能 .
ConcurrentHashMap不会抛出ConcurrentModificationException
如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException
Difference between synchornizedMap and ConcurrentHashMap
Collections.synchornizedMap(HashMap)将返回一个几乎等同于Hashtable的集合,其中Map上的每个修改操作都锁定在Map对象上,而在ConcurrentHashMap的情况下,通过将整个Map划分为基于并发级别的不同分区来实现线程安全性并且只锁定特定部分而不是锁定整个Map .
ConcurrentHashMap不允许空键或空值,而同步HashMap允许一个空键 .
Similar links
Link1
Link2
Performance Comparison
你是对的
HashTable
,你可以忘掉它 .Your article提到这样的事实:虽然HashTable和同步包装类通过一次只允许一个线程访问映射来提供基本的线程安全性,但这不是线程安全的,因为许多复合操作仍然需要额外的同步,例如:
但是,不要认为
ConcurrentHashMap
是具有典型synchronized
块的HashMap
的简单替代方案,如上所示 . 阅读this文章,以更好地了解其复杂性 .ConcurrentHashMap
在项目中需要非常高的并发性时,应该使用ConcurrentHashMap .
没有同步整个 Map 是线程安全的 .
使用锁完成写操作时,读取速度可能非常快 .
对象级别没有锁定 .
在hashmap桶级别,锁定的粒度更精细 .
如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException .
ConcurrentHashMap使用大量锁 .
SynchronizedHashMap
对象级别的同步 .
每次读/写操作都需要获取锁 .
锁定整个集合是一种性能开销 .
这本质上只允许访问整个 Map 的一个线程并阻止所有其他线程 .
可能会引起争用 .
SynchronizedHashMap返回Iterator,它在并发修改时失败 .
source
一般来说,如果你想使用
ConcurrentHashMap
,请确保你准备好错过'updates'(即打印HashMap的内容并不能确保它会打印最新的Map)并使用像
CyclicBarrier
这样的API来确保整个程序生命周期的一致性 .Hashtable
forHashtable
在Collections.synchronizedMap(Map)
中以完全相同的方式存在 - 它们使用非常简单的同步,这意味着只有一个线程可以同时访问 Map .当你有简单的插入和查找时(除非你非常密集地进行),这不是什么大问题,但当你需要迭代整个Map时会出现一个大问题,这可能需要很长时间才能完成一个大的Map - 而一个线程执行此操作,所有其他线程必须等待,如果他们想要插入或查找任何东西 .
ConcurrentHashMap
使用非常复杂的技术来减少同步的需要,并允许多个线程进行并行读取访问而不进行同步,更重要的是,提供了一个不需要同步的Iterator
甚至允许在交互期间修改Map(尽管它不能保证是否返回迭代期间插入的元素) .这里有几个:
1)ConcurrentHashMap仅锁定Map的一部分,但SynchronizedMap锁定整个MAp .
2)ConcurrentHashMap比SynchronizedMap具有更好的性能,并且更具可伸缩性 .
3)如果是多个读者和单个编写者,ConcurrentHashMap是最佳选择 .
本文来自Difference between ConcurrentHashMap and hashtable in Java
ConcurrentHashMap针对并发访问进行了优化 .
访问不会锁定整个 Map ,而是使用更精细的策略,从而提高可扩展性 . 还存在专门用于并发访问的功能性增强,例如,并发迭代器 .
在
ConcurrentHashMap
中,锁定应用于段而不是整个Map . 每个段管理自己的内部哈希表 . 锁仅适用于更新操作 .Collections.synchronizedMap(Map)
同步整个 Map .除了建议之外,我还想发布与
SynchronizedMap
相关的源代码 .要使
Map
线程安全,我们可以使用Collections.synchronizedMap
语句并输入映射实例作为参数 .Collections
中synchronizedMap
的实现如下所示如您所见,输入
Map
对象由SynchronizedMap
对象包装 .让我们深入研究
SynchronizedMap
的实现,SynchronizedMap
的作用可以概括为向输入Map
对象的主方法添加单个锁 . 锁定保护的所有方法不能同时被多个线程访问 . 这意味着put
和get
等正常操作可以由Map
对象中的所有数据同时由单个线程执行 .它使
Map
对象线程现在安全,但在某些情况下性能可能会成为一个问题 .ConcurrentMap
在实现中要复杂得多,我们可以参考Building a better HashMap了解详情 . 简而言之,它的实现考虑了线程安全和性能 .Collections.synchronizedMap()方法同步HashMap的所有方法,并有效地将其简化为一个线程可以一次输入的数据结构,因为它将每个方法锁定在公共锁上 .
在ConcurrentHashMap中,同步完成的方式略有不同 . ConcurrentHashMap不是将每个方法锁定在公共锁上,而是对单独的桶使用单独的锁,因此只锁定Map的一部分 . 默认情况下,有16个存储桶,还有用于单独存储桶的独立锁 . 所以默认的并发级别是16.这意味着理论上任何给定的时间16个线程都可以访问ConcurrentHashMap,如果它们都要分离存储桶的话 .
除了它提供的并发功能之外, one critical feature 还要注意
ConcurrentHashMap
,这是 fail-safe iterator . 我见过开发人员使用ConcurrentHashMap
只是因为他们想要编辑入口集 - 在迭代时放置/删除 .Collections.synchronizedMap(Map)
不提供 fail-safe 迭代器,但它提供 fail-fast 迭代器 . 失败快速迭代器使用迭代期间无法编辑的映射大小的快照 .当你可以使用它时,首选ConcurrentHashMap - 尽管它至少需要Java 5 .
它被设计为在多线程使用时可以很好地扩展 . 当一次只有一个线程访问Map时,性能可能稍微差一些,但当多个线程同时访问映射时,性能会明显提高 .
我找到了blog entry,它从优秀的书Java Concurrency In Practice中再现了一张 table ,我完全推荐 .
Collections.synchronizedMap真正有意义,只有当你需要包含一些具有其他特征的 Map 时,可能是某种有序的 Map ,比如TreeMap .
根据您的需要,使用
ConcurrentHashMap
. 它允许从多个线程并发地修改Map,而无需阻止它们 .Collections.synchronizedMap(map)
创建一个阻塞Map,这会降低性能,尽管确保一致性(如果使用得当) .如果需要确保数据一致性,请使用第二个选项,并且每个线程都需要具有最新的 Map 视图 . 如果性能至关重要,请使用第一个,并且每个线程仅将数据插入到 Map 中,读取发生的频率较低 .
关于锁定机制:
Hashtable
locks the object,而ConcurrentHashMap
锁定only the bucket .如果数据一致性非常重要 - 使用Hashtable或Collections.synchronizedMap(Map) .
如果速度/性能非常重要且数据更新可能受到影响 - 请使用ConcurrentHashMap .
这两者之间的主要区别在于
ConcurrentHashMap
将仅锁定正在更新的部分数据,而其他部分数据可以被其他线程访问 . 但是,Collections.synchronizedMap()
将在更新时锁定所有数据,其他线程只能在释放锁时访问数据 . 如果有许多更新操作和相对少量的读取操作,则应选择ConcurrentHashMap
.另外一个区别是
ConcurrentHashMap
不会保留传入的Map中元素的顺序 . 它在存储数据时类似于HashMap
. 无法保证元素顺序得以保留 . 虽然Collections.synchronizedMap()
将保留传入的Map的元素顺序 . 例如,如果将TreeMap
传递给ConcurrentHashMap
,则ConcurrentHashMap
中的元素顺序可能与TreeMap
中的顺序不同,但Collections.synchronizedMap()
将保留顺序 .此外,
ConcurrentHashMap
可以保证在一个线程更新映射而另一个线程正在遍历从映射获得的迭代器时,没有抛出ConcurrentModificationException
. 但是,Collections.synchronizedMap()
无法保证 .有one post证明了这两者的区别以及
ConcurrentSkipListMap
.