1.这个是初始状态,同时map里面存储的值
2.此时第一个线程开始进行操作,执行到这一步的时候cup去执行其它线程,导致该线程被挂起 do {
Entry next = e.next; // <--假设线程一执行到这里就被调度挂起了,执行其他操作
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
此时线程1的情况,此时线程扩容然后在准备开始给节点重新分配对应的数组的时候被挂起(这个e和next不懂得可以去了解下链表,e代表当前节点,next代表当前节点中存储的下一个节点的地址)
3.当线程1被挂起的时候线程2刚好访问该集合并且完成了扩容操作
这里讲下为什么扩容成这样,因为链表是从第一个节点开始读的所以一开始处理的应该是key=7,然后key=7被存储到数组3的位置,然后遍历到下一个节点key=3,把3存储到数组3的位置,key=7的前面,为什么存储在前面?因为不用遍历到末尾,这样快。然后在处理key=9。
4.同时e还是线程1当前所在节点,然后next是它所指向的下一个节点,此时线程1恢复执行。重点来了!!
第一步,线程1会把自己所在的当前节点(e-第三步,此时会把e插入,同时节点向下遍历(同2) 第四步,会把e节点插入到数组三,应为next指向的是null到尽头了,所以就不需要向下遍历了
于是一个环形链表就形成了,因为key=7的下一个节点是指向key=3的,但是key=3的节点又指向着key=7.
这样就是一个很经典hashMap线程不安全导致的循环依赖,因为是个循环链表,就会导致数组一直重复扩容,导致集合的一个无限大,但是JDK1.8的时候引进了红黑树,当连续扩容32次的时候会转换成红黑树,解决这个循环依赖的问题。



