hashMap产生的循环依赖问题
hashMap是线程不安全的,主要重点讲一下,线程不安全导致的循环依赖问题
假设:hashMap的数组加链表,数组初始长度为2,同时hash的计算方式为key值%2,同时等到链表长度为3的时候开始扩容,扩容方式为原始数组大小*2,此时两个线程对同一个HashMap进行操作
1.这个是初始状态,同时map里面存储的值
2.此时第一个线程开始进行操作,执行到这一步的时候cup去执行其它线程,导致该线程被挂起
do {
Entry<K,V> 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=3,然后key=3被存储到数组3的位置,然后遍历到下一个节点key=7,把7存储到数组3的位置,key=3的前面,为什么存储在前面?因为不用遍历到末尾,这样快。然后在处理key=9。
4.同时e还是线程1当前所在节点,然后next是它所指向的下一个节点,此时线程1恢复执行。
重点来了!!
第一步,线程1会把自己所在的当前节点(e-<key=3,vlue=A>)进行计算,存储到数组三的位置上去,
第二步,会遍历到下一个节点,即 (next-<key=7,vlue=B>),同时把next指向下一个节点(提前获取地址主要是防止链表数据丢失),此时节点e变成(e<key=7,vlue=B>),而因为线程2的操作,导致e的下一个节点是(next<key=3,vlue=A>)
第三步,此时会把e插入,同时节点向下遍历(同2)
第四步,会把e节点插入到数组三,应为next指向的是null到尽头了,所以就不需要向下遍历了
于是一个环形链表就形成了,因为key=7的下一个节点是指向key=3的,但是key=3的节点又指向着key=7.
这样就是一个很经典hashMap线程不安全导致的循环依赖,因为是个循环链表,就会导致数组一直重复扩容,导致集合的一个无限大,但是JDK1.8的时候,把头插法改成了尾插法,同时引进了红黑树,当连续扩容32次的时候会转换成红黑树,解决这个循环依赖的问题,但是还是可能会引起各种线程不安全问题,所以在多线程情况下,尽量使用ConcurrentHashMap。
基础不牢 地动山摇: 附官网了
Jackmat: 兄弟,再发一次呗
Boy407: 看看了那么多文章你的最详细
来自建哥的肯定: 非常的呦西
石臻臻的杂货铺: 奥利给!!