SlideShare une entreprise Scribd logo
1  sur  9
Télécharger pour lire hors ligne
Generated by Foxit PDF Creator © Foxit Software
                                   http://www.foxitsoftware.com For evaluation only.




李鹏同学在blog里写了篇关于HashMap死锁模拟的文章:
http://blog.csdn.net/madding/archive/2010/08/25/5838477.aspx
做个纠正,那个不是死锁问题,而是死循环。


这个问题,我们以前讨论过。
校长之前的blog:http://sdh5724.javaeye.com/blog/619130
和淘宝的毕玄的《分布式Java应用:基础与实践》一书中都提到过 velocity导致cpu 100%
的bug,起因是HashMap的使用不当所致。


在之前的邮件列表里,校长提出过这个问题,当时我没仔细看,不清楚这个问题究竟是对
HashMap的误用,还是HashMap的潜在问题,
当时感觉不太可能是HashMap自身的问题,否则问题大了。应该是属于在并发的场景下错误的
使用了HashMap。


昨天看了李鹏的blog后,觉得这个事情还是应该搞清楚一下;虽然我推测是链表形成闭环,但
没有去证明过。
从网上找了一下:
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 倍于当前容器的数组)
单线程情况。
Generated by Foxit PDF Creator © Foxit Software
                     http://www.foxitsoftware.com For evaluation only.




           E1       E2




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




          Nul
                            null or            X
      X    E1   X   E2


           X
           e             next




执行过一次后的状态:
Generated by Foxit PDF Creator © Foxit Software
                        http://www.foxitsoftware.com For evaluation only.




         Nul            Null              E1
               E2




                e




最终的结果:




         Nul   Null/E          E1          E2




两个线程并发情况下,扩容时可能会创建出 2 个新数组容器。
Generated by Foxit PDF Creator © Foxit Software
                     http://www.foxitsoftware.com For evaluation only.




         Nul
                            null or            X
     X    E1   X    E2



         e
                   next




                          null or            X




顺利的话,最终转移完可能是这样的结果
Generated by Foxit PDF Creator © Foxit Software
                                      http://www.foxitsoftware.com For evaluation only.




             Nul


                   Null/         E1           E2




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

// 扩容操作,从一个数组转移到另一个数组
1 void transfer(Entry[] newTable) {
2      Entry[] src = table;
3      int newCapacity = newTable.length;
4      for (int j = 0; j < src.length; j++) {
5          Entry<K,V> e = src[j];
6          if (e != null) {
7             src[j] = null;
8             do {
9                  Entry<K,V> next = e.next; //假设第一个线程执行到这里
10                  int i = indexFor(e.hash, newCapacity);
11                   e.next = newTable[i];
12                   newTable[i] = e;
Generated by Foxit PDF Creator © Foxit Software
                                           http://www.foxitsoftware.com For evaluation only.




13                    e = next;
14              } while (e != null); // 可能导致死循环
15         }
16     }
17 }


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




               Nul


                     Null/            E1           E2




                                  e              next




后续的步骤如下:

1) 插入 E1 节点,E1 节点的 next 指向新容器索引位置上的值(null 或 entry)
Generated by Foxit PDF Creator © Foxit Software
                                http://www.foxitsoftware.com For evaluation only.




         Nul


               Null/       E1           E2




                       e              next




2) 插入 E2 节点,E2 的 next 指向当前索引位置上的引用值 E1
Generated by Foxit PDF Creator © Foxit Software
                              http://www.foxitsoftware.com For evaluation only.




        Nul


              Null/      E1             E2




                                    e
                      next




3)因为 next 不为 null,链表继续移动,此时 2 节点之间形成了闭环。造成了
死循环。
Generated by Foxit PDF Creator © Foxit Software
                              http://www.foxitsoftware.com For evaluation only.




       Nul


             Null/       E1           E2




                     e




上面只是一种情况,造成单线程死循环,双核 cpu 的话占用率是 50%,还有导致
100%的情况,应该也都是链表的闭环所致。
最终,这并不是 HashMap 的问题,是使用场景的不当,在并发情况下选择非线程
安全的容器是没有保障的。

Contenu connexe

Tendances

Exodus重构和向apollo迁移
Exodus重构和向apollo迁移Exodus重构和向apollo迁移
Exodus重构和向apollo迁移wang hongjiang
 
Scala function-and-closures
Scala function-and-closuresScala function-and-closures
Scala function-and-closureswang hongjiang
 
OpenEJB - 另一個選擇
OpenEJB - 另一個選擇OpenEJB - 另一個選擇
OpenEJB - 另一個選擇Justin Lin
 
深入淺出 Web 容器 - Tomcat 原始碼分析
深入淺出 Web 容器  - Tomcat 原始碼分析深入淺出 Web 容器  - Tomcat 原始碼分析
深入淺出 Web 容器 - Tomcat 原始碼分析Justin Lin
 
Java华为面试题
Java华为面试题Java华为面试题
Java华为面试题yiditushe
 
Java并发编程培训
Java并发编程培训Java并发编程培训
Java并发编程培训longhao
 
Java并发核心编程
Java并发核心编程Java并发核心编程
Java并发核心编程wavefly
 
C++工程实践
C++工程实践C++工程实践
C++工程实践Shuo Chen
 
IOS入门分享
IOS入门分享IOS入门分享
IOS入门分享zenyuhao
 
Spring 2.0 技術手冊第六章 - Hibernate 與 Spring
Spring 2.0 技術手冊第六章 - Hibernate 與 SpringSpring 2.0 技術手冊第六章 - Hibernate 與 Spring
Spring 2.0 技術手冊第六章 - Hibernate 與 SpringJustin Lin
 
线程编程方面
线程编程方面线程编程方面
线程编程方面yiditushe
 
异步编程与浏览器执行模型
异步编程与浏览器执行模型异步编程与浏览器执行模型
异步编程与浏览器执行模型keelii
 
并发编程实践
并发编程实践并发编程实践
并发编程实践longhao
 
Spring 2.0 技術手冊第四章 - Spring AOP
Spring 2.0 技術手冊第四章 - Spring AOPSpring 2.0 技術手冊第四章 - Spring AOP
Spring 2.0 技術手冊第四章 - Spring AOPJustin Lin
 
Free rtos workshop1@nuu
Free rtos workshop1@nuuFree rtos workshop1@nuu
Free rtos workshop1@nuu紀榮 陳
 
Effective linux.3.(diagnosis)
Effective linux.3.(diagnosis)Effective linux.3.(diagnosis)
Effective linux.3.(diagnosis)wang hongjiang
 

Tendances (20)

Exodus重构和向apollo迁移
Exodus重构和向apollo迁移Exodus重构和向apollo迁移
Exodus重构和向apollo迁移
 
Scala function-and-closures
Scala function-and-closuresScala function-and-closures
Scala function-and-closures
 
OpenEJB - 另一個選擇
OpenEJB - 另一個選擇OpenEJB - 另一個選擇
OpenEJB - 另一個選擇
 
functional-scala
functional-scalafunctional-scala
functional-scala
 
深入淺出 Web 容器 - Tomcat 原始碼分析
深入淺出 Web 容器  - Tomcat 原始碼分析深入淺出 Web 容器  - Tomcat 原始碼分析
深入淺出 Web 容器 - Tomcat 原始碼分析
 
Java华为面试题
Java华为面试题Java华为面试题
Java华为面试题
 
Exodus2 大局观
Exodus2 大局观Exodus2 大局观
Exodus2 大局观
 
Java并发编程培训
Java并发编程培训Java并发编程培训
Java并发编程培训
 
Java并发核心编程
Java并发核心编程Java并发核心编程
Java并发核心编程
 
C++工程实践
C++工程实践C++工程实践
C++工程实践
 
IOS入门分享
IOS入门分享IOS入门分享
IOS入门分享
 
Spring 2.0 技術手冊第六章 - Hibernate 與 Spring
Spring 2.0 技術手冊第六章 - Hibernate 與 SpringSpring 2.0 技術手冊第六章 - Hibernate 與 Spring
Spring 2.0 技術手冊第六章 - Hibernate 與 Spring
 
线程编程方面
线程编程方面线程编程方面
线程编程方面
 
异步编程与浏览器执行模型
异步编程与浏览器执行模型异步编程与浏览器执行模型
异步编程与浏览器执行模型
 
并发编程实践
并发编程实践并发编程实践
并发编程实践
 
Spring 2.0 技術手冊第四章 - Spring AOP
Spring 2.0 技術手冊第四章 - Spring AOPSpring 2.0 技術手冊第四章 - Spring AOP
Spring 2.0 技術手冊第四章 - Spring AOP
 
并发控制
并发控制并发控制
并发控制
 
Free rtos workshop1@nuu
Free rtos workshop1@nuuFree rtos workshop1@nuu
Free rtos workshop1@nuu
 
Aswan&hump
Aswan&humpAswan&hump
Aswan&hump
 
Effective linux.3.(diagnosis)
Effective linux.3.(diagnosis)Effective linux.3.(diagnosis)
Effective linux.3.(diagnosis)
 

En vedette

Effective linux.2.(tools)
Effective linux.2.(tools)Effective linux.2.(tools)
Effective linux.2.(tools)wang hongjiang
 
Effective linux.1.(commandline)
Effective linux.1.(commandline)Effective linux.1.(commandline)
Effective linux.1.(commandline)wang hongjiang
 
Real world akka recepies v3
Real world akka recepies v3Real world akka recepies v3
Real world akka recepies v3shinolajla
 
中等创业公司后端技术选型
中等创业公司后端技术选型中等创业公司后端技术选型
中等创业公司后端技术选型wang hongjiang
 
Automatic Scaling Iterative Computations
Automatic Scaling Iterative ComputationsAutomatic Scaling Iterative Computations
Automatic Scaling Iterative ComputationsGuozhang Wang
 
Behavioral Simulations in MapReduce
Behavioral Simulations in MapReduceBehavioral Simulations in MapReduce
Behavioral Simulations in MapReduceGuozhang Wang
 
Apache Kafka, and the Rise of Stream Processing
Apache Kafka, and the Rise of Stream ProcessingApache Kafka, and the Rise of Stream Processing
Apache Kafka, and the Rise of Stream ProcessingGuozhang Wang
 
Apache Kafka at LinkedIn
Apache Kafka at LinkedInApache Kafka at LinkedIn
Apache Kafka at LinkedInGuozhang Wang
 
Building Realtim Data Pipelines with Kafka Connect and Spark Streaming
Building Realtim Data Pipelines with Kafka Connect and Spark StreamingBuilding Realtim Data Pipelines with Kafka Connect and Spark Streaming
Building Realtim Data Pipelines with Kafka Connect and Spark StreamingGuozhang Wang
 
Building a Replicated Logging System with Apache Kafka
Building a Replicated Logging System with Apache KafkaBuilding a Replicated Logging System with Apache Kafka
Building a Replicated Logging System with Apache KafkaGuozhang Wang
 
Building Stream Infrastructure across Multiple Data Centers with Apache Kafka
Building Stream Infrastructure across Multiple Data Centers with Apache KafkaBuilding Stream Infrastructure across Multiple Data Centers with Apache Kafka
Building Stream Infrastructure across Multiple Data Centers with Apache KafkaGuozhang Wang
 
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...Helena Edelson
 
Introduction to Kafka Streams
Introduction to Kafka StreamsIntroduction to Kafka Streams
Introduction to Kafka StreamsGuozhang Wang
 
Shell,信号量以及java进程的退出
Shell,信号量以及java进程的退出Shell,信号量以及java进程的退出
Shell,信号量以及java进程的退出wang hongjiang
 

En vedette (18)

善用工具
善用工具善用工具
善用工具
 
聊一些电影
聊一些电影聊一些电影
聊一些电影
 
Enum开锁
Enum开锁Enum开锁
Enum开锁
 
Effective linux.2.(tools)
Effective linux.2.(tools)Effective linux.2.(tools)
Effective linux.2.(tools)
 
Effective linux.1.(commandline)
Effective linux.1.(commandline)Effective linux.1.(commandline)
Effective linux.1.(commandline)
 
Real world akka recepies v3
Real world akka recepies v3Real world akka recepies v3
Real world akka recepies v3
 
中等创业公司后端技术选型
中等创业公司后端技术选型中等创业公司后端技术选型
中等创业公司后端技术选型
 
Automatic Scaling Iterative Computations
Automatic Scaling Iterative ComputationsAutomatic Scaling Iterative Computations
Automatic Scaling Iterative Computations
 
Behavioral Simulations in MapReduce
Behavioral Simulations in MapReduceBehavioral Simulations in MapReduce
Behavioral Simulations in MapReduce
 
Apache Kafka, and the Rise of Stream Processing
Apache Kafka, and the Rise of Stream ProcessingApache Kafka, and the Rise of Stream Processing
Apache Kafka, and the Rise of Stream Processing
 
Apache Kafka at LinkedIn
Apache Kafka at LinkedInApache Kafka at LinkedIn
Apache Kafka at LinkedIn
 
Building Realtim Data Pipelines with Kafka Connect and Spark Streaming
Building Realtim Data Pipelines with Kafka Connect and Spark StreamingBuilding Realtim Data Pipelines with Kafka Connect and Spark Streaming
Building Realtim Data Pipelines with Kafka Connect and Spark Streaming
 
Building a Replicated Logging System with Apache Kafka
Building a Replicated Logging System with Apache KafkaBuilding a Replicated Logging System with Apache Kafka
Building a Replicated Logging System with Apache Kafka
 
Building Stream Infrastructure across Multiple Data Centers with Apache Kafka
Building Stream Infrastructure across Multiple Data Centers with Apache KafkaBuilding Stream Infrastructure across Multiple Data Centers with Apache Kafka
Building Stream Infrastructure across Multiple Data Centers with Apache Kafka
 
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...
Building Reactive Distributed Systems For Streaming Big Data, Analytics & Mac...
 
Introduction to Kafka Streams
Introduction to Kafka StreamsIntroduction to Kafka Streams
Introduction to Kafka Streams
 
Shell,信号量以及java进程的退出
Shell,信号量以及java进程的退出Shell,信号量以及java进程的退出
Shell,信号量以及java进程的退出
 
Scala类型系统
Scala类型系统Scala类型系统
Scala类型系统
 

Hash map导致cpu100% 的分析

  • 1. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. 李鹏同学在blog里写了篇关于HashMap死锁模拟的文章: http://blog.csdn.net/madding/archive/2010/08/25/5838477.aspx 做个纠正,那个不是死锁问题,而是死循环。 这个问题,我们以前讨论过。 校长之前的blog:http://sdh5724.javaeye.com/blog/619130 和淘宝的毕玄的《分布式Java应用:基础与实践》一书中都提到过 velocity导致cpu 100% 的bug,起因是HashMap的使用不当所致。 在之前的邮件列表里,校长提出过这个问题,当时我没仔细看,不清楚这个问题究竟是对 HashMap的误用,还是HashMap的潜在问题, 当时感觉不太可能是HashMap自身的问题,否则问题大了。应该是属于在并发的场景下错误的 使用了HashMap。 昨天看了李鹏的blog后,觉得这个事情还是应该搞清楚一下;虽然我推测是链表形成闭环,但 没有去证明过。 从网上找了一下: 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 倍于当前容器的数组) 单线程情况。
  • 2. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. E1 E2 我们分析数据转移的过程,主要是链表的转移。 Nul null or X X E1 X E2 X e next 执行过一次后的状态:
  • 3. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul Null E1 E2 e 最终的结果: Nul Null/E E1 E2 两个线程并发情况下,扩容时可能会创建出 2 个新数组容器。
  • 4. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul null or X X E1 X E2 e next null or X 顺利的话,最终转移完可能是这样的结果
  • 5. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul Null/ E1 E2 但并发情况下,出现死循环的可能场景是什么呢? 还要详细的分析一下代码,下面的代码中重点在 do/while 循环结构中(完成链 表的转移)。 // 扩容操作,从一个数组转移到另一个数组 1 void transfer(Entry[] newTable) { 2 Entry[] src = table; 3 int newCapacity = newTable.length; 4 for (int j = 0; j < src.length; j++) { 5 Entry<K,V> e = src[j]; 6 if (e != null) { 7 src[j] = null; 8 do { 9 Entry<K,V> next = e.next; //假设第一个线程执行到这里 10 int i = indexFor(e.hash, newCapacity); 11 e.next = newTable[i]; 12 newTable[i] = e;
  • 6. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. 13 e = next; 14 } while (e != null); // 可能导致死循环 15 } 16 } 17 } 2 个线程并发情况下, 当线程 1 执行到上面第 9 行时,而线程 2 已经完成了一 轮 do/while 操作,那么它的状态如下图: (上面的数组时线程 1 的,已经完成了链表数据的转移;下面的是线程 2 的,它 即将开始进行对链表数据的转移,此时它记录 E1 和 E2 的首位已经被线程 1 翻 转了) Nul Null/ E1 E2 e next 后续的步骤如下: 1) 插入 E1 节点,E1 节点的 next 指向新容器索引位置上的值(null 或 entry)
  • 7. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul Null/ E1 E2 e next 2) 插入 E2 节点,E2 的 next 指向当前索引位置上的引用值 E1
  • 8. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul Null/ E1 E2 e next 3)因为 next 不为 null,链表继续移动,此时 2 节点之间形成了闭环。造成了 死循环。
  • 9. Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. Nul Null/ E1 E2 e 上面只是一种情况,造成单线程死循环,双核 cpu 的话占用率是 50%,还有导致 100%的情况,应该也都是链表的闭环所致。 最终,这并不是 HashMap 的问题,是使用场景的不当,在并发情况下选择非线程 安全的容器是没有保障的。