最近项目中出现了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。
相关推荐
在给定的代码片段中,并没有直接涉及到多线程的创建与使用,但是我们可以基于这段代码来讨论如何在类似场景下引入多线程。 ### 使用Swing构建图形用户界面 给定的代码展示了如何使用Java Swing库来构建一个简单的...
在Java编程语言中,多线程是实现并发执行任务的关键技术。这个压缩包中的内容,"Java多线程矩阵...通过对代码的阅读和分析,我们可以深入理解Java多线程在解决实际问题中的应用,同时也能掌握矩阵运算的并行化策略。
在现代计算环境中,多线程技术对于充分利用CPU资源、提高应用程序响应速度以及实现复杂并发控制至关重要。本文将深入探讨Java多线程的各个方面,包括基础知识、创建线程、线程同步与通信、死锁问题以及线程池。 1. ...
在这个示例中,`ArrayList`和`HashMap`本身不是线程安全的容器,直接在多线程环境中使用这些容器进行迭代操作时可能会引发数据不一致问题。为了保证线程安全性,可以考虑使用`Collections.synchronizedList`、`...
3. **线程同步**:为了解决多线程环境中的数据安全问题,Java提供了synchronized关键字、volatile变量、Lock接口(如ReentrantLock)等同步机制。 二、线程控制 1. **线程的启动、暂停与停止**:start()方法启动...
在Java中,多线程主要用于处理那些可以并行运行的任务,比如网络I/O、用户界面更新和计算密集型任务。理解并熟练掌握Java多线程对于任何Java开发者来说都是至关重要的。 首先,我们需要了解线程的基本概念。线程是...
如果使用集合类,如`ArrayList`或`HashMap`,在多线程环境下可能会遇到并发修改问题。此时,可以使用线程安全的集合类,如`Collections.synchronizedList()`,或者使用`ConcurrentHashMap`等。 6. **死锁和活锁**...
- **实现方式**: 在Java中实现多线程只需要提供线程将要执行的代码。 - **实现步骤**: - 继承`java.lang.Thread`类或实现`Runnable`接口。 - 重写`run()`方法,该方法包含线程要执行的代码。 - 创建线程对象并...
在多线程环境下,传统的数据结构(如ArrayList、HashMap)可能引发线程安全问题。为此,Java提供了一系列并发容器(如ConcurrentHashMap、CopyOnWriteArrayList),它们内部实现了线程安全的机制,可以在多线程环境...
课程可能还会涉及线程安全的数据结构,如ArrayList与Vector、HashMap与ConcurrentHashMap的区别,以及如何在多线程环境下正确地使用它们。线程局部变量(ThreadLocal)也是一个可能讨论的话题,它为每个线程提供独立...
Java多线程是Java开发中的核心技能之一,尤其在面试中,对于一线大厂的面试者来说,深入理解和掌握多线程的相关知识点至关重要。以下是一些关键的Java多线程面试知识点: 1. **自旋锁**:自旋锁是一种等待机制,当...
在Java编程语言中,多线程是至关重要的一个特性,尤其在现代高性能计算和分布式系统设计中。通过理解和掌握多线程技术,开发者可以充分利用计算机的多核处理器资源,提高程序的并发性和响应速度。本篇文章将深入探讨...
线程死锁是多线程编程中一个严重的问题,它发生在两个或多个线程相互等待对方释放资源,导致它们都无法继续执行。CPU过高通常与过度的计算、无尽循环、死锁、线程竞争状态等问题相关。在Java编程中,WeakHashMap是一...
java多线程和多进程 以下内容包含:华东师范大学的多线程讲解 及 马士兵多线程讲解 马士兵多线程讲解迁移位置:仓库 DOCRecord\ResteasyComplexDemo\src\pers\lishbo\timetask 1.多进程: 1.当前的操作系统都是多...
Java集合框架中的某些类(如`ArrayList`、`HashMap`)默认是非线程安全的,即它们没有提供同步机制来防止多线程环境下的并发修改异常。为了解决这个问题,可以采取以下措施: - 使用同步容器:如`Vector`、`...
但是,在多线程环境下使用头插法会引起循环链表的问题,从而导致CPU飙升。 - **JDK8的HashMap**底层数据结构仍为数组+链表,但新增了红黑树结构。在JDK8中,链表采用尾插法(tail-insertion),即新插入的元素被...
在Java中,多线程可以通过继承Thread类或实现Runnable接口来实现。下面我们将深入探讨Java多线程编程的相关知识点。 1. **线程的创建** - **继承Thread类**:创建一个新类,继承自Thread类,然后重写run()方法,...
通过以上步骤,可以找到导致Java进程cpu占用率过高的原因,并采取相应的措施来解决问题,从而提高系统性能和服务器稳定性。 在解决Java进程cpu占用率过高问题时,需要注意以下几点: * 使用top命令来查看占用cpu的...
此外,Java并发编程还包括对并发容器的使用,如ArrayList、LinkedList、HashSet、HashMap等基础容器在并发环境下可能存在问题,Java提供了一些线程安全的容器,如Vector、HashTable以及java.util.concurrent包下的...