最近项目中出现了Tomcat占用CPU100%的情况,原以为是代码中出现死循环,后台使用jstack做了dump,发现是系统中不合理使用HashMap导致出现了死循环(注意不是死锁)。
产生这个死循环的根源在于对一个未保护的共享变量 — 一个"HashMap"数据结构的操作。当在所有操作的方法上加了"synchronized"后,一切恢复了正常。
这算jvm的bug吗?应该说不是的,这个现象很早以前就报告出来了(详细见:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6423457)。Sun的工程师并不认为这是bug,而是建议在这样的场景下应采用"ConcurrentHashMap",
回复中的原话:
This is a classic symptom of an incorrectly synchronized use of HashMap. Clearly, the submitters need to use a thread-safe HashMap. If they upgraded to Java 5, they could just use ConcurrentHashMap.
所以在开发过程中应当注意这点,在多线程的环境下,尽量使用ConcurrentHashMap。
可能出现问题的地方是在扩容的时候
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
这个方法本身没有问题,问题出在transfer(newTable);这个方法是用来移动oldTable里的数据到newTable里。
/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { //(1) Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { //(2) Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); //(3) e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
下面分析可能出现的情况,假设原来oldTable里存放a1,a2的hash值是一样的,那么entry链表顺序是:
P1:oldTable[i]->a1->a2->null
P2:oldTable[i]->a1->a2->null
线程P1运行到(1)下面这行时,e=a1(a1.next=a2),继续运行到(2)下面时,next=a2。这个时候切换到线程P2,线程P2执行完这个链表的循环。如果恰a1,a2在新的table中的hash值又是一样的,那么此时的链表顺序是:
主存:newTable[i]->a2->a1->null
注意这个时候,a1,a2连接顺序已经反了。现在cpu重新切回P1,在(3)这行以后:e.next = newTable[i];即:a1.next=newTable[i];
newTable[i]=a1;
e=a2;
开始第二次while循环(e=a2,next=a1):
a2.next=newTable[i];//也就是a2.next=a1
newTable[i]=a2
e=a1
开始第三次while循环(e=a1,next=null)
a1.next=newTable[i];//也就是a1.next=a2
这个时候a1.next=a2,a2.next=a1,形成回环了,这样就造成了死循环,在get操作的时候next永远不为null,造成死循环。
可以看到很偶然的情况下会出现死循环,不过一旦出现后果是非常严重的,多线程的环境还是应该用ConcurrentHashMap。
相关推荐
接着,文档讨论了负载因子的概念及其取值为0.75的原因,以及HashMap在多线程环境下的线程安全性问题,指出了可能导致CPU利用率达到100%的并发扩容问题,并提出了解决方案。 此外,文档介绍了哈希冲突的概念和解决...
接着,文件讨论了负载因子的概念及其取值为0.75的原因,以及HashMap在多线程环境下的线程安全性问题,指出了可能导致CPU利用率达到100%的并发扩容问题,并提出了解决方案。 此外,文件介绍了哈希冲突的概念和解决...
java多线程和多进程 以下内容包含:华东师范大学的多线程讲解 及 马士兵多线程讲解 马士兵多线程讲解迁移位置:仓库 DOCRecord\ResteasyComplexDemo\src\pers\lishbo\timetask 1.多进程: 1.当前的操作系统都是多...
同时拥有俩个或者多个线程,如果线程在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时 "存在" 的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时程序中的每个线程都将...
多线程模块: 并发的基本概念: 同时拥有一个或者多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存。 一个线程对应者Cpu的一个内核。现在系统都是多核处理器,同时支持多个并发一起执行程序...
在淘宝内网里看到同事发了贴说了一个CPU被的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环。这个事情我4、5年前也经历过,本来觉得没什么好写的,...
3)什么是多线程中的上下文切换? 4)死锁和活锁、死锁和饥饿的区别? 5) Java 中使用了什么线程调度算法? 6) Java 中的线程调度器是什么? 7) 如何处理线程中未处理的异常? 8) 什么是线程组,为什么不建议在 Java...
51.3. 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 24 51.4. 线程同步的方法。 24 51.5. java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 25...
多线程问题? concurrentHashMap?读写操作实现?锁机制?实现维持map大小的LRU算法? volatile关键字?volatile修饰数组?修饰对象? 找两条链表公共节点? 如何判断循环链表? mysql索引?B+数搜索复杂度? mysql...
25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................
1)多线程(ThreadLocal(问了父子线程怎么共享数据 interitableThreadLocals)、lock和sync区别(问HashMap1.7、1.8区别时带出)、 AQS原理(执行过程源码,入队出队的细节,源码细节)、CountDownLatch和 ...
25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................
多线程和线程池 HTTP、HTTPS、TCP/IP、Socket通信、三次握手四次挥手过程 设计模式(六大基本原则、项目中常用的设计模式、手写单例等) 断点续传 volatile理解,JMM中主存和工作内存到底是啥?和JVM各个部分怎么个...