如果我有两个多线程同时访问一个HashMap,但是可以保证它们永远不会同时访问同一个键,那么这样做还会导致竞争条件吗?
如果我有两个多线程同时访问一个HashMap,但是可以保证它们永远不会同时访问同一个键,那么这样做还会导致竞争条件吗?
如果你以任何方式更改了一个
HashMap
,那么你的代码就会破坏。
他是正确的。如果没有同步更新 HashMap
,即使线程使用不相交的键集,也会出现问题。以下只是可能出现的问题之一:
如果一个线程执行了 put
,那么另一个线程可能会看到哈希映射大小的旧值。
如果一个线程使用与第二个线程的键当前在同一个哈希桶中的键进行 put
,则第二个线程的映射条目可能会丢失,暂时或永久地。这取决于哈希链(或其他结构)的实现方式。
当一个线程执行触发表重建的 put
时,另一个线程可能会看到 hashtable 数组引用、它的大小、它的内容或哈希链的瞬态或旧版本。混乱可能会随之而来。
当一个线程为与某些其他线程使用的键发生碰撞的键执行 put
时,并且后者线程为其键执行 put
时,则后者可能会看到哈希链引用的旧副本。混乱可能会随之而来。
当一个线程使用与某些其他线程的键之一发生碰撞的键探测表时,它可能会在链上遇到该键。它将对该键调用 equals,并且如果线程不同步,则 equals 方法可能会在该键中遇到过时状态。
如果同时有两个线程执行 put
或 remove
请求,则存在许多竞争条件的机会。
我可以想到三个解决方案:
ConcurrentHashMap
。HashMap
,但在外部进行同步,例如使用原始互斥锁、Lock
对象等。但需注意,这可能会因锁争用导致并发瓶颈。HashMap
。如果线程确实具有不重叠的键集,则从算法的角度来看,它们无需共享单个Map。确实,如果您的算法涉及线程在某些时候迭代Map的键、值或条目,则将单个Map拆分成多个Map可能会大幅加速该处理部分。1- 我们无法列举所有可能出现的问题。首先,我们无法预测所有JVM在各平台上如何处理JMM的未指定方面...。但您不应该依赖那种信息。您只需要知道像这样使用HashMap
是基本错误的。即使您尚未观察到故障的症状,这样的应用程序也是有缺陷的。
只需使用ConcurrentHashMap即可。ConcurrentHashMap使用多个锁来覆盖一系列哈希桶,以减少争用锁的机会。获取未争夺的锁会对性能产生微不足道的影响。
回答您最初的问题:根据javadoc的说法,只要映射的结构没有改变,您就可以放心使用。这意味着完全不删除元素,也不添加不在映射中的新键。替换与现有键相关联的值是可以的。
如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。(结构修改是添加或删除一个或多个映射的任何操作;仅更改实例已经包含的键的关联值不是结构修改。)
虽然它不保证可见性。因此,您必须愿意接受偶尔检索过时的关联。
HashMap
不应更改,并且所有更改(初始构造)都应在任何读者开始访问HashMap
之前完成。HashMap
,那么你的代码就会出现问题。@Stephen C提供了非常好的解释。Collections.unmodifiableMap()
来确保你的HashMap永远不会被更改。由HashMap
指向的对象也不应更改,因此积极使用final
关键字可以帮助你。ConcurrentHashMap
在大多数情况下是最佳选择。unmodifiableMap
确保客户端不能修改该映射。但它无法确保底层映射不被更改。 - Pete Kirkham在两个线程没有适当同步的情况下修改HashMap可能会很容易地导致竞态条件。
put()
导致内部表的调整大小时,这需要一些时间,而另一个线程继续写入旧表。put()
对于模表大小相等的哈希码导致相同桶的更新。(实际上,哈希码和桶索引之间的关系更加复杂,但仍然可能发生冲突。)