问题:
1、JDK1.7为啥为产生并发死链问题
- 并发,即多线程同时访问HashMap
需要知道的一些前提知识:
1、 JDK1.7是采用有插法进行节点的添加的
2、 HashMap的扩容长度为原来的一倍
JDK1.7单线程下的扩容:
1、进行节点的转移前,需要把数组长度变为原来的一倍
2、节点(Entry)转移的源代码
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length; //得到新数组的长度
for (Entry<K,V> e : table) { //第一步:遍历整个数组对应下标下的链表,e代表一个节点
while(null != e) { //当e==null时,则该链表遍历完了,继续遍历下一数组下标的链表
Entry<K,V> next = e.next; //第二步:先把e节点的下一节点存起来
if (rehash) { //得到新的hash值
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity); //在新数组下得到新的数组下标
e.next = newTable[i]; //第三步:将e的next指针指向新数组下标的位置
newTable[i] = e; //第四步:将该数组下标的节点变为e节点
e = next; //第五步:遍历链表的下一节点
}
}
}
3、开始演示
- 看我贴出来的源码把重要的步骤分为五步
- 1、假设已经遍历到我们3这个节点对应下标的链表了。那么此时第一步的e节点,和第二步的next节点分别指向如下图所示的节点。
- 2、执行第三步:将e的next指针指向新数组下标的位置,如图所示:
- 3、执行第四步:将该数组下标的节点变为e节点,如图所示:
- 4、执行第五步:遍历链表的下一节点,如图所示:
- 5、此时一次while循环已经完毕,进入下次循环,执行第一步和第二步,执行后如图所示:
- 6、按照以上步骤,我们可以将节点2和节点1都移到新的数组中,全部移到新数组后如图所示:
4、总结
- JDK1.7的转移后元素的位置将反了。
JDK1.7多线程下的扩容:
1、我这里假设有两个线程分别为t1和t2,而且两个线程同时读到了需要扩容,这时候,就会导致会创建两个新的数组,而且同时要去操作这个老的数组的节点1,2,3,假设这时候两个线程都执行了第一步和第二步,这时候的状态如图所示:
2、假设这时候操作系统的时间片没有分到t2线程,只有t1线程在运行,那么按照单线程情况下转移的过程得到t1线程完成转移后如图所示:
3、接下来是t2线程运行了,那么此时移动的便是t1线程新创建的表中的节点1,2,3;现在来详细说明一下这些步骤;
-
1)上面讲了,t2线程已经完成了源码内容的第一步和第二步的操作,接下来进行第三步:将e2的next2指针指向t2线程创建的新数组下标的位置,如图所示:
-
2)接下来执行第四步操作:将该数组下标的节点变为e2节点,如图所示:
-
接下来执行第五步操作:遍历链表的下一节点,如图所示:
-
进入新循环,再执行第一步和第二步的操作,执行后结果如图所示:
-
接下来执行第三步操作,结果如图所示:
-
接下来执行第四步操作,结果如图所示:
-
接下来执行第五步操作遍历下一节点(重点来了),如图所示:
-
再次进入下一次循环,执行第一步和第二步的操作,如图所示:
-
接下来执行第三步操作,如图所示:
-
接下来执行第四步操作,如图所示:
-
接下来也不用演示了,节点2和节点3会一直循环下去,可以导致程序一直执行,问题就很严重了,电脑直接卡死,可能还会抛出OutOfMemoryException;而且你们有没有发现,节点1给丢弃了,导致丢值的现象。
4、扩展:
虽然JDK1.8的HashMap采用尾插法避免了上述的问题,但仍旧是线程不安全的;所以如果要使用线程安全的HashMap,建议使用ConcurrentHashMap。
5、留言:
- 有不对的地方还望大家积极留言纠错。