HashMap致使死循环问题

  虽然我推测是链表造成闭环,但 没有去证实过。从网上找了一下: http://blog.csdn.net/autoinspired/archive/2008/07/16/2662290.aspx 里面也有提到:数组

  产生这个死循环的根源在于对一个未保护的共享变量 — 一个”HashMap”数据结构的操做。当在 全部操做的方法上加了”synchronized”后,一切恢复了正常。检查”HashMap”(Java SE 5.0)的源 码,咱们发现有潜在的破坏其内部结构最终形成死循环的可能。在下面的代码中,若是咱们使得 HashMap中的entries进入循环,那 么”e.next()”永远都不会为null。安全

  不只get()方法会这样,put()以及其余对外暴露的方法都会有这个风险,这算jvm的bug吗?应该说不是的,这个现象很早之前就报告出来了(详细见: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457)。Sun的工程师并不认为这 是bug,而是建议在这样的场景下应用”ConcurrentHashMap”,在构建可扩展的系统时应将这点 归入规范中。数据结构

  这篇翻译提到了对HashMap的误用,但它没有点破HashMap内部结构在什么样误用状况下怎么被 破坏的;我想要一个有力的场景来弄清楚。再从李鹏的blog来看,用了2个线程来put就模拟出来了,最后堆栈是在 transfer 方法上(该方法是数据扩容时将数据从旧容器转移到新容器)。 仔细分析了一下里面的代码,基本得出了缘由,证实了我以前的推测。并发

假设扩容时的一个场景以下(右边的容器是一个长度 2 倍于当前容器的数组) 单线程状况。jvm

咱们分析数据转移的过程,主要是链表的转移spa

执行过一次后的状态:.net

最终的结果:线程

两个线程并发状况下,扩容时可能会建立出 2 个新数组容器翻译

顺利的话,最终转移完多是这样的结果3d

但并发状况下,出现死循环的可能场景是什么呢? 还要详细的分析一下代码,下面的代码中重点在 do/while 循环结构中(完成链 表的转移)。

 1 // 扩容操做,从一个数组转移到另外一个数组
 2 void transfer(Entry[] newTable) { 
 3     Entry[] src = table;
 4     int newCapacity = newTable.length; 
 5     for (int j = 0; j < src.length; j++) {
 6         Entry<K,V> e = src[j]; 
 7         if (e != null) {
 8             src[j] = null; 
 9             do {
10                 Entry<K,V> next = e.next; //假设第一个线程执行到这里 
11                 int i = indexFor(e.hash, newCapacity);
12                 e.next = newTable[i];
13                 newTable[i] = e;
14                 e = next;
15             } while (e != null); // 可能致使死循环
16         }
17     }
18 }

2 个线程并发状况下, 当线程 1 执行到上面第 9 行时,而线程 2 已经完成了一 轮 do/while 操做,那么它的状态以下图:
(上面的数组时线程 1 的,已经完成了链表数据的转移;下面的是线程 2 的,它 即将开始进行对链表数据的转移,此时它记录 E1 和 E2 的首位已经被线程 1 翻 转了)

后续的步骤以下:

1) 插入 E1 节点,E1 节点的 next 指向新容器索引位置上的值(null 或 entry)

2) 插入 E2 节点,E2 的 next 指向当前索引位置上的引用值 E1

3)由于 next 不为 null,链表继续移动,此时 2 节点之间造成了闭环。形成了 死循环。

上面只是一种状况,形成单线程死循环,双核 cpu 的话占用率是 50%,还有致使 100%的状况,应该也都是链表的闭环所致。

最终,这并非 HashMap 的问题,是使用场景的不当,在并发状况下选择非线程 安全的容器是没有保障的。

 

转自:https://blog.csdn.net/zhao9tian/article/details/38976933

相关文章
相关标签/搜索