HashMap JDK7产生死锁的原因

HashMap是非线程安全,死锁一般都是产生于并发情况下。

JDK7中的HashMap

HashMap底层维护一个数组,数组中的每一项都是一个Entry

transient Entry<K,V>[] table;

向 HashMap 中所放置的对象实际上是存储在该数组当中; 而Map中的key,value则以Entry的形式存放在数组中。

这个Entry应该放在数组的哪一个位置上(这个位置通常称为位桶或者hash桶,即hash值相同的Entry会放在同一位置,用链表相连),是通过key的hashCode来计算的。通过hash计算出来的值将会使用indexFor方法找到它应该所在的table下标。

出现死锁原因

refresh核心代码如下:

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
            	//1, 获取旧表的下一个元素
                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. 对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点。
  3. 循环直到链表节点全部转移
  4. 循环直到所有索引数组全部转移

遍历旧数组,将旧数组元素通过头插法的方式,迁移到新数组的。经过这几步会发现转移的时候是逆序的,A->B->C迁移后会变成C->B->A,HashMap 的死锁问题就出在这个transfer()函数上。

例子

我们假设有二个线程T1、T2,HashMap容量为2,T1线程放入key A、B、C、D、E。

在T1线程中A、B、C Hash值相同,于是形成一个链接,假设为A->B->C,而D、E Hash值不同,于是容量不足,需要新建一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中(refresh)。

这时T2线程进来了,T1执行到代码Entry<K,V> next = e.next线程A挂起,此刻e指向A,next指向B

T2线程也准备放入新的key,这时也发现容量不足进行refresh。T2开始执行transfer函数中的while循环,会把原来的table变成一个table(线程T2自己的栈中),再写入到内存中。因为转移的时候是逆序的,refresh之后原来的链表结构假设为C->B->A。

T1继续执行执行完一个while循环后,e=B,T1的链表是A。

继续执行T1,此时e=B且B->A,所以next=A,执行一个while循环后,e=A,T1的链表是B->A。

继续执行T1,此时e=A,所以next=null,执行一个while循环后,e=null,线程A的链表是A->B->A

这时就形成A.next=B,B.next=A的环形链表,一旦取值进入这个环形链表就会陷入死循环。

jdk1.8版本相关优化

  • 使用尾插法,消除出现循环链表的情况
  • 链表过长后,转化为红黑树,提高查询效率
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值