我有seen something解释了HashMap在多线程中无法保持原因的原因。
它说,当调整大小时,链表中的整个对象序列都会反转,并显示示例:
例如,假设有3个具有相同哈希码的密钥,因此存储在桶内的链表中[以下格式在object_value(current_address,next_address)中] 初始结构:1(100,200) - > 2(200,300) - > 3(300,null)
在通过线程1:3(300,200)调整大小后 - > 2(200,100) - > 1(100,null)
当thread-2开始调整大小时,它再次从第一个元素开始,将它放在头部:
1(100,300) - > 3(300,200) - > 2(200,100)==>这成为下一次插入的无限循环,线程在这里挂起。
我对这个例子感到很困惑,
初始结构:1 - > 2 - > 3
线程1:3-> 2-> 1
线程2:1 - > 3 - > 2为什么?
有人可以帮我分析一下这个例子或者展示一个更详细的例子吗?感谢。
答案 0 :(得分:1)
我不清楚你在问什么。
您是否有兴趣知道为什么HashMap不是线程安全的?或者您只是想知道调整大小期间“逆转”效果的原因(这是线程不安全的原因之一)?
对于后一个问题(这是你在问题中明确提出的问题),原因如下:
通过检查HashMap的源代码,有一个transfer()
方法负责将条目从旧表移动到新表:
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
相反的是上述逻辑的副作用(看起来更接近do-while循环并且它应该不难理解)。
如果您问他们为什么要让订单反转,那么您最好问作者。但是我可以告诉你,他们并不打算“逆转”这个命令。由于HashMap没有关于迭代顺序的保证,实现不需要维护任何顺序。只要结果正确,实现者就可以选择最简单,最快捷的方式来实现调整大小逻辑。目前的逻辑是他们的选择。
更新:如果你只是想知道一个非线程安全的案例,还有其他更明显的事情。
例如,在向Map添加条目时,逻辑如下:首先计算要放入条目的索引,将其添加到表中的索引,如果表是“full”,则执行resize。
可能存在这样的情况:第一个线程试图添加一个条目,原始表大小为100,然后哈希码为101,然后它找出索引为1.
此时,另一个线程进入,并向表中添加一个条目,发现该表是“full”,然后执行resize。新表的大小现在是200。
然后在这个时候,线程1进入实际将条目放入表并尝试放入索引1的步骤。但是,对于大小为200的新表,要放置的正确索引应该是101而不是1.
结果是地图导致了损坏的状态。
还有更多不同的线程无法保证的例子。
对于您提到的给定示例。以下是它可能导致问题的具体示例:
假设现有哈希表:
[0] -> E1 -> E2 -> E3 -> null
[1]
Resize将执行以下操作:
- Create a new table
(old table)
[0] -> E1 -> E2 -> E3 -> null
[1]
(new table)
[0]
[1]
[2]
[3]
- iterate thru the original entries, and put it one by one
(Put E1 to new table)
[0] - E2 -> E3
[1] \
\
v
[0] -> E1 ->null
[1]
[2]
[3]
(Put E2 to new table)
[0] ------ E3
[1] \
\
v
[0] -> E2 -> E1 ->null
[1]
[2]
[3]
此时您将看到旧表的索引0仍然指向E1
如果另一个线程进入并尝试进行调整大小,在这种中间状态下调整大小可能会导致各种问题:原始文章中的next
错误,或者在结果表中缺少条目等。< / p>
答案 1 :(得分:0)
来自Oracle Java文档(http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html)
请注意,此实现未同步。如果有多个线程 同时访问哈希映射,以及至少一个线程 从结构上修改地图,必须在外部进行同步。 (一个 结构修改是添加或删除一个或多个的任何操作 更多映射;只是改变与键相关的值 实例已经包含的不是结构修改。)这是 通常通过自然地同步某个对象来完成 封装地图。如果不存在这样的对象,则应该是地图 &#34;包裹&#34;使用Collections.synchronizedMap方法。这是最好的 在创建时完成,以防止意外的不同步访问 地图:
Map m = Collections.synchronizedMap(new HashMap(...));
当您向hashmap添加元素时,它的内部结构会发生变化,因此您无法信任hashmap中元素的排序。如果要维护排序,请使用TreeMap。
答案 2 :(得分:0)