HashMap为什么会造成死循环?
HashMap插入数据的原理:
JDK1.7中HashMap插入数据的底层逻辑是数组加链表的方式,在插入数据的时候采用的是头插法,新插入的数据会从链表的头节点进行插入。
因此HashMap在正常情况下的扩容,旧的HashMap会变成新的HashMap,如图:
| 0 | 1 | Α |
B
C
| 0 | 1 | C | ... |
B
A
原因:
并发场景下HashMap扩容导致的死循环:
1:线程启动:此时有t1和t2两个线程都准备对HashMap进行扩容,此时t1和t2都指向的是链表的头节点A,t1和t2的下一个节点分别是t1.next和t2.next,都指向B节点。
2:开始扩容:假设t2进入休眠状态,线程t1执行结束后t2开始唤醒。t1从A节点开始执行,因为HashMap采用的是头插法,t1执行结束后链表中节点顺序发生了变化,顺序刚好与t1执行前顺序相反,t1执行完成后链表编程如下图。t1完成后t2被唤醒执行时,t2开始的节点依然是指向A,此时t1在节点B的位置,而t1的下一个节点是A,而t2只是在A的位置,t2的下一个节点是B。然后t1执行的顺序是B到A,而t2执行的顺序是A到B,这样A节点和B节点就形成死循环
| 0 | 1 | C | ... |
B
A
解决方案:
1:使用线程安全的ConcurrentHashMap替代HashMap(推荐使用)
2:使用线程安全的容器HashTable替代(性能低,不推荐使用)
3:使用Syschronized或lock加锁之后再进行操作,相当于多线程排队执行,但是会影响性能(不推荐使用)
总结:
HashMap死循环只发生在JDK1.7中,jdk1.7版本中在头插法加上链表加上多线程加上扩容几个情形累加在一起时,就会形成一个死循环。
在jdk1.8HashMap中改为尾插法,解决了链表死循环的问题。
浙公网安备 33010602011771号