对于不同的密钥,HashMap是否是线程安全的?

时间:2010-04-22 06:24:41

标签: java multithreading thread-safety hashmap

如果我有两个多线程访问HashMap,但保证他们永远不会同时访问同一个密钥,那还能导致竞争条件吗?

4 个答案:

答案 0 :(得分:83)

在@ dotsid的回答中,他说:

  

如果以任何方式更改HashMap,那么您的代码就会被破坏。

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

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

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

  • 当一个线程为一个与某个其他线程使用的某个键冲突的键执行put,而后一个线程为其键执行put时,后者可能会看到哈希链引用的陈旧副本。可能会发生混乱。

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

如果您有两个线程同时执行putremove请求,则竞争条件会有很多机会。

我可以想到三个解决方案:

  1. 使用ConcurrentHashMap
  2. 使用常规HashMap但在外部同步;例如使用原始互斥体,Lock个对象,等等。
  3. 为每个帖子使用不同的HashMap。如果线程确实有一组不相交的键,那么就不需要(从算法的角度来看)它们共享一个Map。实际上,如果您的算法涉及线程在某个时刻迭代地图的键,值或条目,则将单个地图拆分为多个地图可以为处理的这一部分提供显着的加速。

答案 1 :(得分:26)

只需使用ConcurrentHashMap。 ConcurrentHashMap使用多个锁,这些锁覆盖了一系列散列桶,以减少锁争用的可能性。获得无争议锁定会对性能产生微不足道的影响。

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

  

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

虽然它不能保证可见度。所以你必须愿意接受偶尔检索陈旧的联想。

答案 2 :(得分:5)

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

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

编辑:如果第一种情况是您的实际情况,我建议您使用Collections.unmodifiableMap()来确保您的HashMap永远不会更改。 HashMap指向的对象也不应更改,因此使用final关键字进行攻击可以帮助您。

正如@Lars Andren所说,ConcurrentHashMap是大多数情况下的最佳选择。

答案 3 :(得分:3)

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

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