死循环问题发生在JDK1.7版本中,造成这个问题主要是由于HashMap自身的运行机制,加上并发操作,从而导致了死循环。 在JDK1.7中HashMap的底层数据实现是数组+链表的方式
初始状态 :
而JDK7
的 HashMap在数据添加时使用的是 头插入 ,如下图所示:
正常情况下的扩容应该是下面的情况 :
数组在扩容以后, 使用头插法将旧的元素复制到新的数组上.
死循环是因为并发HashMap扩容导致的,并发扩容的第一步,线程T1 和线程T2 要对HashMap进行扩容操作,此时T1和T2指向的是链表的头结点元素A,而T1和T2的下一个节点,也就是T1.next和T2.next指向的是B节点,如下图所示:
死循环的第二步操作是,线程T2时间片用完进入休眠状态,而线程T1开始执行扩容操作,一直到线程T1扩容完成后,线程T2才被唤醒,扩容之后的场景如下图所示:
从上图可知线程T1执行之后,因为是头插法,所以HashMap的顺序已经发生了改变,但线程T2对于发生的一切是不可知的,所以它的指向元素依然没变,如上图展示的那样,T2指向的是A元素,T2.next指向的节点是B元素
当线程T1执行完,而线程T2恢复执行时,死循环就建立了,如下图所示:
因为T1执行完扩容之后B节点的下一个节点是A,而T2线程指向的首节点是A,第二个节点是B,这个顺序刚好和T1扩完容完之后的节点顺序是相反的。T1执行完之后的顺序是B到A,而T2的顺序是A到B,这样A节点和B节点就形成死循环了,这就是HashMap死循环导致的原因
HashMap死循环的常用解决方案有以下3个:
补充 : JDK 8 虽然将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),但仍不意味着能 够在多线程环境下能够安全扩容,还会出现其它问题(如扩容丢数据)
HashMap死循环发生在JDK1.7版本中,形成死循环的原因是HashMap
在JDK1.7使用的是头插法,头插法+链表+多线程并发+HashMap扩容,这几个点加在一起就形成了HashMap的死循环,解决死锁可以采用线程安全容器ConcurrentHashMap
替代。
本文作者:兀坐晴窗独饮茶
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!