在调整大小时,多线程环境中的Hashmap

时间:2013-04-12 14:24:24

标签: java hashmap

我正在关注一个教程,它基本上解释了在多线程环境中调整Hashmap大小时发生的竞争条件的原因:

  

在Java中,如果两个线程同时发现现在HashMap需要调整大小并且它们都尝试调整大小。在Java中调整HashMap大小的过程中,存储在链表中的存储桶中的元素在迁移到新存储桶时按顺序颠倒,因为java HashMap不会在尾部附加新元素,而是在头部附加新元素避免尾部穿越。如果竞争条件发生,那么你将最终得到一个无限循环

阅读本文后我有两个问题:

  1. 为什么每个存储桶的链表按顺序颠倒?
  2. 我可以看到可能存在竞争条件,但看不出无限循环是怎么来的?是因为一个线程可能会将元素头部追加到尾部,而另一个线程以相反的顺序执行它?
  3. 请帮我澄清一下,非常感谢!

3 个答案:

答案 0 :(得分:9)

第一个问题的答案在引用文字中:

  

“因为java HashMap不会在尾部附加新元素,而是在头部附加新元素以避免尾部遍历”

如果HashMap以插入顺序存储它们,则必须在每次插入时遍历列表,或者存储指向列表末尾的额外指针(并保持它)。无论如何以插入顺序将元素存储在存储桶中都不会带来任何好处(至少我想不到任何好处)。

您的第二个问题的答案依赖于此:

http://mailinator.blogspot.hu/2009/06/beautiful-race-condition.html

答案 1 :(得分:3)

实际上至少有一个与重组相关的竞争条件。看看这个代码片段(来自Sun JDK7):

boolean oldAltHashing = this.useAltHashing;
this.useAltHashing |= sun.misc.VM.isBooted() && (this.newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ this.useAltHashing;
transfer(newTable, rehash);
this.table = newTable;

这里线程T1可能以rehash = true结束,线程T2最终以rehash = false结束(假设T1已经改变了this.useAltHashing的值)。

现在,猜猜哪个线程会写this.table - 你不知道,也可以。所以,无论你是否获得一致的内部状态,这都是运气问题。

无论如何,正如我在评论中提到的那样,它不应该在多线程环境中使用HashMap 。不起作用。无论是因为这个,还是因为其他原因。上面只是一个例子,为什么你不应该试图违背合同。

答案 2 :(得分:0)

我不知道这个例子是否有效。很明显,它是特定于实现的。我认为它也错过了更大的图景。

HashMap的{​​{3}}明确状态(强调他们的):

  

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

如果你违约,所有的赌注都会被取消。地图可以任意,未指明的方式自由爆炸。