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

Hel*_*iro 80 java multithreading hashmap thread-safety

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

Ste*_*n C 89

在@ dotsid的回答中他说:

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

他是对的.即使线程使用不相交的密钥集,即使没有同步也更新的HashMap也会中断.以下是一些可能出错的事情.

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

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

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

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

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

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

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


Tim*_*der 27

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

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

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

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


Den*_*nov 6

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

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

编辑:如果第一种情况是你的实际情况,我建议你使用Collections.unmodifiableMap()shure,你的HashMap永远不会改变.指向的对象HashMap也不应该更改,因此使用final关键字积极可以帮助您.

正如@Lars Andren所说,ConcurrentHashMap在大多数情况下,这是最好的选择.

  • ConcurrentHashMap是我认为的最佳选择.我没有推荐它的唯一原因,因为作者没有问过它:)由于CAS操作它的吞吐量较少,但是作为并发编程的黄金法则说:"做得对,只有这样才能快速":) (2认同)

Chr*_*rau 5

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

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