欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

为什么HashMap是线程不安全的?

程序员文章站 2024-03-26 09:36:35
...

假设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;
        }
    }
}

为什么HashMap是线程不安全的?
假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。
为什么HashMap是线程不安全的?
假设 线程2 在执行到 Entry<K,V>next=e.next; 之后,cpu时间片用完了,这时 变量e 指向 节点a变量next 指向 节点b。线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点。

第一步,移动节点a:
为什么HashMap是线程不安全的?
第二步,移动节点b:
为什么HashMap是线程不安全的?
第三步,移动节点c:
为什么HashMap是线程不安全的?
这个时候线程1的时间片用完,内部的table还没有完全生成新的newTable,线程2 开始执行,这时内部的引用关系如下:
为什么HashMap是线程不安全的?
这时,在 线程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;

执行以后的引用关系如下所示:
为什么HashMap是线程不安全的?
执行后,变量e 指向 节点b,因为e不是null,则继续执行循环体,执行后的引用关系:
为什么HashMap是线程不安全的?
变量e 又重新指回 节点a,只能继续执行循环体:
1. 执行完 Entry<K,V>next=e.next,目前 节点a 没有 next ,所以 变量next 指向 null
2. e.next=newTable[i] 其中 newTable[i] 指向 节点b,那就是把 节点anext 指向了 节点b,这样a和b就相互引用了,形成了一个环。
3. newTable[i]=e节点a 放到了数组下标i 位置。
4. e=next变量e 赋值为 null,因为第一步中 变量next 就是指向 null
为什么HashMap是线程不安全的?
节点a节点b 互相引用,形成了一个环。当在数组该位置get寻找对应的key时,就发生了死循环。另外,如果线程2把newTable 设置成到内部的table,节点c 的数据就丢了,看来还有数据遗失的问题。总之,千万不要在多线程写时使用HashMap,单写多读是没有问题的。


参考自:https://mp.weixin.qq.com/s/i_r1aLlQR8qTz7kz8vKk6w?