如果我有两个多线程访问HashMap,但保证他们永远不会同时访问同一个密钥,那么是否仍然会导致争用情况?对于不同的密钥,HashMap是线程安全的吗?
回答
在@ dotsid的回答,他这样说:
如果你以任何方式改变一个HashMap然后你的代码很简单。
他是对的。如果线程正在使用不相交的密钥集,那么在没有同步的情况下更新的HashMap将会中断甚至。以下是一些可能出错的事情。
如果一个线程执行
put
,则另一个线程可能会看到散列表大小的陈旧值。当一个线程执行一个
put
触发表重建时,另一个线程可能会看到哈希表数组引用,其大小,其内容或哈希链的暂时或过时的版本。混乱可能随之而来。当一个线程执行针对与其他某些线程使用的一些关键碰撞的关键一
put
,而后者线程做了put
其关键,那么后者可能会看到的哈希链引用过时副本。混乱可能随之而来。当一个线程使用与某个其他线程的键冲突的键探测表时,它可能会遇到该链上的该键。它将在该键上调用equals,并且如果线程未同步,那么equals方法可能会在该键中遇到陈旧状态。
如果你有两个线程同时做put
或remove
要求,有竞争条件众多的机会。
我能想到的三种解决方案:
- 使用
ConcurrentHashMap
。 - 使用常规
HashMap
但在外部同步;例如使用原始互斥体,对象等等。 - 为每个线程使用不同的
HashMap
。如果线程真的有一组不相交的键,那么应该没有必要(从算法的角度来看)他们共享一个Map。事实上,如果你的算法涉及线程在某个点迭代键值,值或者地图条目,那么将单个地图分割成多个地图可以为该部分处理提供显着的加速。
这取决于你在“访问”下的意思。如果您只是阅读,只要在“happens-before”规则下保证的数据的可见性,您甚至可以阅读相同的密钥。这意味着HashMap
不应该改变,并且在任何阅读器开始访问HashMap
之前应完成所有更改(初始构造)。
如果您以任何方式更改HashMap
,那么您的代码就会被破坏。 @Stephen C为什么提供了很好的解释。
编辑:如果第一种情况是您的实际情况,我建议您使用Collections.unmodifiableMap()
以确保您的HashMap从不改变。被HashMap
指向的对象也不应该改变,所以使用final
关键字可以帮助你。
而@Lars Andren说,在大多数情况下,ConcurrentHashMap
是最佳选择。
ConcurrentHashMap怎么样? – 2010-04-22 06:37:19
ConcurrentHashMap在我看来是最好的选择。我没有推荐它的唯一原因,因为作者没有问它:) 由于CAS操作,它的吞吐量较低,但作为并发编程的黄金法则说:“做对了,然后才做它快速“:) – 2010-04-22 06:43:17
'unmodifiableMap'确保客户端无法更改地图。它没有做任何事情来确保底层的地图没有改变。 – 2010-04-22 07:42:16
只需使用ConcurrentHashMap即可。 ConcurrentHashMap使用多个锁来涵盖一系列散列桶,以减少锁被争用的可能性。获得无争议的锁有很小的性能影响。
回答你的原始问题:根据javadoc,只要地图的结构没有改变,你很好。这意味着根本不会删除元素,也不会添加地图中尚未包含的新键。替换与现有的键相关的值是好的。
如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则它必须在外部同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的密钥关联的值不是结构修改。)
尽管它对可见性没有任何保证。所以你必须愿意接受偶尔检索陈旧的协会。
+1为技术准确性(如果不是最佳实践:-) – finnw 2011-05-21 14:36:09
修改一个HashMap而没有从两个线程进行适当的同步可能很容易导致竞争条件。
- 当
put()
导致内部表的大小调整时,这需要一些时间,另一个线程继续写入旧表。 - 对于不同的密钥,两个
put()
会导致相同桶的更新,如果密钥的哈希码相等于模大小的话。 (实际上,哈希码和铲斗指数之间的关系比较复杂,但仍可能会发生冲突。)
4.使用常规HashMap,并使用ReentrantReadWriteLock,以允许多个并发读者,但排他性写作业务。 – 2010-04-22 07:30:06
2.的子表达式:-)我认为不需要枚举可以同步访问常规HashMap的各种方法。 – 2010-04-22 07:46:05
可以使用同步**块吗? – 2017-03-24 13:38:45