Hel*_*iro 80 java multithreading hashmap thread-safety
如果我有两个多线程访问HashMap,但保证他们永远不会同时访问同一个密钥,那还能导致竞争条件吗?
Ste*_*n C 89
在@ dotsid的回答中他说:
如果以任何方式更改HashMap,那么您的代码就会被破坏.
他是对的.即使线程使用不相交的密钥集,即使没有同步也更新的HashMap也会中断.以下是一些可能出错的事情.
如果一个线程执行a put
,则另一个线程可能会看到hashmap大小的陈旧值.
当一个线程put
触发重建表时,另一个线程可能会看到哈希表数组引用的瞬态或过时版本,其大小,内容或哈希链.混乱可能随之而来.
当一个线程put
为一个与某个其他线程使用的某个键冲突的键执行一个put
,而后一个线程为其键执行了一个键时,后者可能会看到一个哈希链引用的陈旧副本.混乱可能随之而来.
当一个线程使用与某个其他线程的某个键冲突的键探测该表时,它可能会在链上遇到该键.它将在该键上调用equals,如果线程未同步,则equals方法可能会遇到该键中的陈旧状态.
如果你有两个线程同时做put
或remove
要求,竞争条件有很多机会.
我可以想到三个解决方案:
ConcurrentHashMap
.HashMap
但外部同步; 例如,使用原始互斥体,Lock
对象等.HashMap
为每个线程使用不同的.如果线程确实有一组不相交的键,那么就不需要(从算法的角度来看)它们共享一个Map.实际上,如果您的算法涉及线程在某个点迭代地图的键,值或条目,则将单个地图拆分为多个地图可以为该部分处理提供显着的加速.Tim*_*der 27
只需使用ConcurrentHashMap.ConcurrentHashMap使用多个锁,这些锁覆盖了一系列散列桶,以减少锁争用的可能性.获得无争议锁定会对性能产生微不足道的影响.
回答你的原始问题:根据javadoc,只要地图的结构没有改变,你就可以了.这意味着根本没有删除元素,也没有添加尚未在地图中的新键.替换与现有键关联的值很好.
如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步.(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改.)
虽然它不能保证可见性.所以你必须愿意接受偶尔检索陈旧的联想.
这取决于你在"访问"下的含义.如果您只是阅读,只要在"先发生 "规则下保证数据的可见性,您甚至可以读取相同的密钥.这意味着不HashMap
应该更改,所有更改(初始构造)应该在任何读者开始访问之前完成HashMap
.
如果您HashMap
以任何方式更改,那么您的代码就会被破坏.@Stephen C提供了很好的解释原因.
编辑:如果第一种情况是你的实际情况,我建议你使用Collections.unmodifiableMap()
shure,你的HashMap永远不会改变.指向的对象HashMap
也不应该更改,因此使用final
关键字积极可以帮助您.
正如@Lars Andren所说,ConcurrentHashMap
在大多数情况下,这是最好的选择.
在两个线程没有正确同步的情况下修改 HashMap 可能很容易导致竞争条件。
put()
导致内部表的大小调整时,这需要一些时间,并且另一个线程继续写入旧表。put()
如果键的哈希码等于表大小的模,则两个不同键会导致同一存储桶的更新。(实际上,hashcode和bucket索引之间的关系更复杂,但仍然可能会发生冲突。)