Java 对于不同的键,HashMap 线程安全吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2688629/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 10:51:32  来源:igfitidea点击:

Is a HashMap thread-safe for different keys?

javamultithreadingthread-safetyhashmap

提问by Helder S Ribeiro

If I have two multiple threads accessing a HashMap, but guarantee that they'll never be accessing the same key at the same time, could that still lead to a race condition?

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

采纳答案by Stephen C

In @dotsid's answer he says this:

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

If you change a HashMap in any way then your code is simply broken.

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

He is correct. A HashMap that is updated without synchronization will break evenif the threads are using disjoint sets of keys. Here are some of the things that can go wrong.

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

  • If one thread does a put, then another thread may see a stale value for the hashmap's size.

  • When a thread does a putthat triggers a rebuild of the table, another thread may see transient or stale versions of the hashtable array reference, its size, its contents or the hash chains. Chaos may ensue.

  • When a thread does a putfor a key that collides with some key used by some other thread, and the latter thread does a putfor its key, then the latter might see a stale copy of hash chain reference. Chaos may ensue.

  • When one thread probes the table with a key that collides with one of some other thread's keys, it may encounter that key on the chain. It will call equals on that key, and if the threads are not synchronized, the equals method may encounter stale state in that key.

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

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

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

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

And if you have two threads simultaneously doing putor removerequests, there are numerous opportunities for race conditions.

如果您有两个线程同时执行putremove请求,则存在大量竞争条件的机会。

I can think of three solutions:

我能想到三个解决方案:

  1. Use a ConcurrentHashMap.
  2. Use a regular HashMapbut synchronize on the outside; e.g. using primitive mutexes, Lockobjects, etcetera.
  3. Use a different HashMapfor each thread. If the threads really have a disjoint set of keys, then there should be no need (from an algorithmic perspective) for them to share a single Map. Indeed, if your algorithms involve the threads iterating the keys, values or entries of the map at some point, splitting the single map into multiple maps could give a significant speedup for that part of the processing.
  1. 使用一个ConcurrentHashMap.
  2. 使用常规HashMap但在外部同步;例如,使用原始互斥体、Lock对象等。
  3. HashMap对每个线程使用不同的。如果线程确实有一组不相交的键,那么它们就不需要(从算法的角度来看)共享一个 Map。实际上,如果您的算法涉及在某个时刻迭代映射的键、值或条目的线程,那么将单个映射拆分为多个映射可以显着提高该部分的处理速度。

回答by Denis Bazhenov

It depends on what you mean under "accessing". If you just reading, you can read even the same keys as long as visibility of data guarantied under "happens-before" rules. This means that HashMapshouldn't change and all changes (initial constructions) should be completed before any reader start to accessing HashMap.

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

If you change a HashMapin any way then your code is simply broken. @Stephen C provides very good explanation why.

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

EDIT: If the first case is your actual situation, I recommend you to use Collections.unmodifiableMap()to be shure that your HashMap is never changed. Objects which are pointed by HashMapshould not change also, so aggressive using finalkeyword can help you.

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

And as @Lars Andren says, ConcurrentHashMapis best choice in most cases.

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

回答by Tim Bender

Just use a ConcurrentHashMap. The ConcurrentHashMap uses multiple locks which cover a range of hash buckets to reduce the chances of a lock being contested. There is a marginal performance impact to acquiring an uncontested lock.

只需使用 ConcurrentHashMap。ConcurrentHashMap 使用多个锁来覆盖一定范围的哈希桶,以减少争用锁的机会。获取无争用锁对性能的影响很小。

To answer your original question: According to the javadoc, as long as the structure of the map doesn't change, your are fine. This mean no removing elements at all and no adding new keys that are not already in the map. Replacing the value associated with existing keys is fine.

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

If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)

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

Though it makes no guarantees about visibility. So you have to be willing to accept retrieving stale associations occasionally.

虽然它不能保证可见性。因此,您必须愿意接受偶尔检索陈旧的关联。

回答by Christian Semrau

Modifying a HashMap without proper synchronization from two threads may easily lead to a race condition.

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

  • When a put()leads to a resize of the internal table, this takes some time and the other thread continues to write to the old table.
  • Two put()for different keys lead to an update of the same bucket if the keys' hashcodes are equal modulo the table size. (Actually, the relation between hashcode and bucket index is more complicated, but collisions may still occur.)
  • 当 aput()导致内部表的大小调整时,这需要一些时间,另一个线程继续写入旧表。
  • put()如果键的哈希码等于表大小的模数,则两个不同的键会导致更新同一个存储桶。(其实hashcode和bucket index的关系比较复杂,但是还是有可能会发生冲突。)