对于不同的密钥,HashMap是线程安全的吗?
如果我有两个multithreading访问一个HashMap,但保证他们永远不会同时访问同一个键,那么这是否仍然会导致竞争状态呢?
在@ dotsid的答案,他这样说:
如果你以任何方式改变一个HashMap,那么你的代码就会被破坏。
他是对的。 即使线程正在使用不相交的密钥集, 即使未同步更新的HashMap也会中断。 以下是一些可能出错的事情。
-
如果一个线程执行
put
,则另一个线程可能会看到散列表大小的陈旧值。 -
当一个线程执行一个
put
触发器重build表时,另一个线程可能会看到hashtable数组引用,其大小,内容或哈希链的暂时或过时的版本。 混乱可能随之而来。 -
当一个线程
put
一个与某个其他线程使用的某个键相冲突的键时,后一个线程put
了一个键,那么后者可能会看到一个散列链参考的陈旧副本。 混乱可能随之而来。 -
当一个线程使用与某个其他线程的键冲突的键来检测该表时,可能会遇到该链上的该键。 它将在该键上调用equals,并且如果线程不同步,equals方法可能会在该键中遇到陈旧状态。
而如果你有两个线程同时做出要求,那么竞争条件就有很多机会。
我可以想到三个解决scheme:
- 使用
ConcurrentHashMap
。 - 使用常规的
HashMap
但在外部同步; 例如使用原始互斥体,Lock
对象等等。 - 为每个线程使用不同的
HashMap
。 如果线程真的有一组不相交的键,那么应该没有必要从algorithm的angular度来分享一个Map。 事实上,如果你的algorithm涉及线程迭代关键字,值或某个点的地图条目,那么将单个地图拆分成多个地图可以为该部分处理提供显着的加速。
只需使用ConcurrentHashMap。 ConcurrentHashMap使用多个锁来覆盖一系列散列桶,以减less锁争议的机会。 获得无争议的锁有一个边际性能的影响。
回答你原来的问题:根据javadoc,只要地图的结构不变,你就没事了。 这意味着根本不需要删除元素,也不需要添加不在地图中的新键。 replace与现有的键相关的值是好的。
如果多个线程同时访问哈希映射,并且至less有一个线程在结构上修改了映射,则它必须在外部同步。 (结构修改是添加或删除一个或多个映射的任何操作;只是更改与实例已包含的关键字相关联的值不是结构修改。)
虽然它不能保证能见度。 所以你必须愿意接受偶尔检索陈旧的协会。
这取决于你在“访问”下的意思。 如果你只是阅读,只要在“ 发生之前 ”规则下保证的数据的可见性,甚至可以读取相同的密钥。 这意味着HashMap
不应该改变,所有的改变(初始结构)都应该在任何读者开始访问HashMap
之前完成。
如果你以任何方式改变一个HashMap
,那么你的代码就会被破坏。 @Stephen C为什么提供了很好的解释。
编辑:如果第一种情况是你的实际情况,我build议你使用Collections.unmodifiableMap()
来确保你的HashMap从不改变。 由HashMap
指向的对象也不应该改变,所以使用final
关键字可以帮助你。
正如@Lars Andren所说的,在大多数情况下, ConcurrentHashMap
是最好的select。
如果在两个线程之间没有正确同步的情况下修改HashMap可能会很容易导致竞争状态。
- 当
put()
导致内部表的大小调整时,这需要一些时间,另一个线程继续写入旧表。 - 对于不同的密钥,两个
put()
会导致相同桶的更新,如果密钥的hashcode等于表的大小。 (实际上,哈希码和桶索引之间的关系比较复杂,但是仍然会发生冲突。)