2010-04-22 63 views

回答

72

在@ dotsid的回答,他这样说:

如果你以任何方式改变一个HashMap然后你的代码很简单。

他是对的。如果线程正在使用不相交的密钥集,那么在没有同步的情况下更新的HashMap将会中断甚至。以下是一些可能出错的事情。

  • 如果一个线程执行put,则另一个线程可能会看到散列表大小的陈旧值。

  • 当一个线程执行一个put触发表重建时,另一个线程可能会看到哈希表数组引用,其大小,其内容或哈希链的暂时或过时的版本。混乱可能随之而来。

  • 当一个线程执行针对与其他某些线程使用的一些关键碰撞的关键一put,而后者线程做了put其关键,那么后者可能会看到的哈希链引用过时副本。混乱可能随之而来。

  • 当一个线程使用与某个其他线程的键冲突的键探测表时,它可能会遇到该链上的该键。它将在该键上调用equals,并且如果线程未同步,那么equals方法可能会在该键中遇到陈旧状态。

如果你有两个线程同时做putremove要求,有竞争条件众多的机会。

我能想到的三种解决方案:

  1. 使用ConcurrentHashMap
  2. 使用常规HashMap但在外部同步;例如使用原始互斥体,对象等等。
  3. 为每个线程使用不同的HashMap。如果线程真的有一组不相交的键,那么应该没有必要(从算法的角度来看)他们共享一个Map。事实上,如果你的算法涉及线程在某个点迭代键值,值或者地图条目,那么将单个地图分割成多个地图可以为该部分处理提供显着的加速。
+2

4.使用常规HashMap,并使用ReentrantReadWriteLock,以允许多个并发读者,但排他性写作业务。 – 2010-04-22 07:30:06

+2

2.的子表达式:-)我认为不需要枚举可以同步访问常规HashMap的各种方法。 – 2010-04-22 07:46:05

+0

可以使用同步**块吗? – 2017-03-24 13:38:45

5

这取决于你在“访问”下的意思。如果您只是阅读,只要在“happens-before”规则下保证的数据的可见性,您甚至可以阅读相同的密钥。这意味着HashMap不应该改变,并且在任何阅读器开始访问HashMap之前应完成所有更改(初始构造)。

如果您以任何方式更改HashMap,那么您的代码就会被破坏。 @Stephen C为什么提供了很好的解释。

编辑:如果第一种情况是您的实际情况,我建议您使用Collections.unmodifiableMap()以确保您的HashMap从不改变。被HashMap指向的对象也不应该改变,所以使用final关键字可以帮助你。

而@Lars Andren说,在大多数情况下,ConcurrentHashMap是最佳选择。

+0

ConcurrentHashMap怎么样? – 2010-04-22 06:37:19

+2

ConcurrentHashMap在我看来是最好的选择。我没有推荐它的唯一原因,因为作者没有问它:) 由于CAS操作,它的吞吐量较低,但作为并发编程的黄金法则说:“做对了,然后才做它快速“:) – 2010-04-22 06:43:17

+0

'unmodifiableMap'确保客户端无法更改地图。它没有做任何事情来确保底层的地图没有改变。 – 2010-04-22 07:42:16

24

只需使用ConcurrentHashMap即可。 ConcurrentHashMap使用多个锁来涵盖一系列散列桶,以减少锁被争用的可能性。获得无争议的锁有很小的性能影响。

回答你的原始问题:根据javadoc,只要地图的结构没有改变,你很好。这意味着根本不会删除元素,也不会添加地图中尚未包含的新键。替换与现有的键相关的值是好的。

如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则它必须在外部同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的密钥关联的值不是结构修改。)

尽管它对可见性没有任何保证。所以你必须愿意接受偶尔检索陈旧的协会。

+0

+1为技术准确性(如果不是最佳实践:-) – finnw 2011-05-21 14:36:09

3

修改一个HashMap而没有从两个线程进行适当的同步可能很容易导致竞争条件。

  • put()导致内部表的大小调整时,这需要一些时间,另一个线程继续写入旧表。
  • 对于不同的密钥,两个put()会导致相同桶的更新,如果密钥的哈希码相等于模大小的话。 (实际上,哈希码和铲斗指数之间的关系比较复杂,但仍可能会发生冲突。)