HashMap线程安全问题

本文深入探讨了HashMap在并发环境下的线程不安全问题,特别是在扩容操作时可能出现的数组链表环状结构。通过分析HashMap的transfer方法,展示了在特定条件下,两个线程并发扩容可能导致的链表环形结构,从而引发死循环,影响后续的get和put操作。此问题强调了在多线程场景下使用线程安全的数据结构如ConcurrentHashMap的重要性。
摘要由CSDN通过智能技术生成

前言

都知道HashMap是线程不安全的,现在说说他是怎么样的一个不安全法。
先给出结论:

HashMap的扩容操作在多线程场景下会出现问题——数组上的链表会成环。

下面来大致看看它是如何形成环的。

transfer

在扩容的操作中,有一步是转移元素——将原数组的元素转移到新数组。以下就是转移的源代码

    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.有两个线程共同使用一个HashMap,并且在put元素时同时达到了扩容条件,开始进行元素的转移。
2.这个HashMap的容量假设为4,扩容后为8,并且假设某一条链上的元素扩容后的索引还都是一样的(即还在一条链上)
3.两个线程先后执行到了第五行
4.第一个线程先执行第五行下面的代码。

明确以下事情:

扩容时各个线程都会在自己线程内部新建一个数组,完成扩容后将这个数组赋值给table
各个线程都会有自己的指针去标记原table的元素。

扩容之前如下图:
在这里插入图片描述
当两个线程执行到第五行的时候,如下图:
在这里插入图片描述
两个线程分别有(e1,next1)和(e2,next2)两套各自的线程内变量指向线程共享的table数组。之后线程开始将table的元素转移到自己线程内新建的数组中,第一个线程先完成了这个操作,如下:
在这里插入图片描述
注意第二个线程中的e2,next2这时候的位置(指向entry1和entry2)由于线程1将entry1entry2进行了转移,所以这俩指针位置现在看起来是也跟着过来了。
这时候线程2开始转移,线程而并不知道entry1entry2已经换了位置了,所以线程2相当于把线程一中的数组的元素已到了自己线程中的数组中,移动完后如下图:

在这里插入图片描述
可以看到在线程2中的数组中出现了环。随后线程2会将这个带环的数组赋值给HashMap 的table变量。在之后的get或put的时候(需要遍历链表的时候)就会出现死循环。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值