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

如果我有两个multithreading访问一个HashMap,但保证他们永远不会同时访问同一个键,那么这是否仍然会导致竞争状态呢?

在@ dotsid的答案,他这样说:

如果你以任何方式改变一个HashMap,那么你的代码就会被破坏。

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

  • 如果一个线程执行put ,则另一个线程可能会看到散列表大小的陈旧值。

  • 当一个线程执行一个put触发器重build表时,另一个线程可能会看到hashtable数组引用,其大小,内容或哈希链的暂时或过时的版本。 混乱可能随之而来。

  • 当一个线程put一个与某个其他线程使用的某个键相冲突的键时,后一个线程put了一个键,那么后者可能会看到一个散列链参考的陈旧副本。 混乱可能随之而来。

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

而如果你有两个线程同时做出要求,那么竞争条件就有很多机会。

我可以想到三个解决scheme:

  1. 使用ConcurrentHashMap
  2. 使用常规的HashMap但在外部同步; 例如使用原始互斥体, Lock对象等等。
  3. 为每个线程使用不同的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等于表的大小。 (实际上,哈希码和桶索引之间的关系比较复杂,但是仍然会发生冲突。)