为什么HashMap是线程不安全的?
假设HashMap初始化大小为4,负载因子是1。已经先后插入c、b、a三个节点,当插入第四个节点时,HashMap就会扩容,并且对之前的三个节点rehash。以下是节点插入(头插法:每次插入链表的头部)的逻辑:
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for(Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if(rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。
假设 线程2 在执行到 Entry<K,V>next=e.next;
之后,cpu时间片用完了,这时 变量e
指向 节点a
,变量next
指向 节点b
。线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点。
第一步,移动节点a:
第二步,移动节点b:
第三步,移动节点c:
这个时候线程1的时间片用完,内部的table还没有完全生成新的newTable,线程2 开始执行,这时内部的引用关系如下:
这时,在 线程2 中,变量e
指向 节点a
,变量next
指向 节点b
,开始执行循环体的剩余代码:
if(rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
执行以后的引用关系如下所示:
执行后,变量e
指向 节点b
,因为e不是null,则继续执行循环体,执行后的引用关系: 变量e
又重新指回 节点a
,只能继续执行循环体:
1. 执行完 Entry<K,V>next=e.next
,目前 节点a
没有 next
,所以 变量next
指向 null
。
2. e.next=newTable[i]
其中 newTable[i]
指向 节点b
,那就是把 节点a
的 next
指向了 节点b
,这样a和b就相互引用了,形成了一个环。
3. newTable[i]=e
把 节点a
放到了数组下标i
位置。
4. e=next
把 变量e
赋值为 null
,因为第一步中 变量next
就是指向 null
。 节点a
和 节点b
互相引用,形成了一个环。当在数组该位置get寻找对应的key时,就发生了死循环。另外,如果线程2把newTable
设置成到内部的table,节点c
的数据就丢了,看来还有数据遗失的问题。总之,千万不要在多线程写时使用HashMap,单写多读是没有问题的。
上一篇: 清华大学的C++设计课程(郭炜/刘家瑛老师)整理笔记
下一篇: npm安装vue