我们JDK1.7的链表的插入方法使用的是头插法,也就是当发生hash冲突之后,新元素将会加到链表的头部,然后整个链表向下移动一位,让新元素成为链表的第一个元素;
那么我们再根据这个来看JDK1.7的扩容:
1在添加元素的时候,也就是进入到addEntry里面会判断是否需要进行扩容
在单线程的情况下扩容:
1.生成一个新的数组,新数组长度为老数组长度的两倍
2.进入核心方法:transfer(在这里面进行核心扩容操作)
3.循环整个老数组的每个头结点,再嵌套一个while循环,根据每个头结点去循环它的下一个节点(链表)重新进行hash计算,生成新的hashCode值;
4.再根据当前的这个节点(从头结点开始循环)的hash值和新数组的长度生成新的索引值
5.将当前节点的下一个节点指向当前索引位置的节点(第一次为空),再将当前节点(头结点),添加到当前索引位置中(e.next=newTable[I]);
newTable[I]=e:
然后,这个e=next也就是e这个时候为2了,进入下一次循环
6.然后再进行链表的下一个节点的插入,同样也是将当前节点的下一个节点指向当前索引位置的节点(也就是前一个添加的节点),也就是链表的头节点,然后再将当前节点添加到当前索引位置中(也就是整体向下移动)直到下一个节点为空才会停止
e.next=newTable[i]:
newTable[i]=e:
那么这样就导致新链表的顺序是颠倒过来的,比如原来是1->2->3,那么新链表就是变成了3->2->1;
那么我们思考如果在多线程的情况下呢?
如果在多线程的情况下,两个线程同时进行扩容,那么这两个线程都分别生成了一个新的数组;
两个同时进入到各自的transfer方法中
这个时候,两个线程需要同时对原来的数组中的数据进行转移,但是这个时候需要注意:这个旧数组会被这两个线程共用!
那么什么时候会出现循环链表的问题呢?
假设就数组的一条链表为3>2>1;并且线程A和线程B同时都拿到了当前旧数组的当前节点和下一个节点;
但是线程B在重新计算HashCode值的地方忽然发生了阻塞,而线程A正常运行,注意:线程B的e和next是已经指向了当前节点和写一个节点了
而这个时候线程A一直正常运行,直到这条链表复制完成,这个时候在线程A里面该链表就变成1->2->3;
这个时候线程B忽然活了过来,这个时候注意,e节点还是3,next节点还是指向的2,并且旧数组这个时候已经相当于消失了,这个时候就会变成下面这样
但是我们发现,旧数组其实已经被线程A转换完毕了,整个链表的顺序发生了反转,因此这个时候线程B的e和next的指向已经出现了问题,但是还是要依旧往下走;
1.e.next=newTable[I] 根据由当前节点的next节点去指向当前索引位置的节点;(没有问题)
2.newTable[I] = e 将当前节点赋值给当前索引位置的节点;(没有问题)
3.e=next:把next节点重新赋值给e进行下一次循环,这个时候的e就为2;
4.Entry<K,V> next = e.next:将e的next节点赋值给next,然后又因为线程A已经转换完了,2的next节点其实是3,因此这个时候next又变为了3,然后正常执行完毕,2->3,进行下一次循环,这个时候执行到e=next,当前的e又会变成3;
5.这个时候Entry<K,V>next = e,next:这个循环next就是null了,然后执行到e.next=newTable[I]的时候我们会发现,出现了问题,这个时候的newTable[i]其实就是2,也就是3的下一个节点又去指向2,那么就出现了循环链表了
然后线程B的next又是null了,就直接结束循环了,这就是循环链表的问题。