多线程环境下HashMap导致CPU100%

简介: 多线程环境下HashMap导致CPU100%

引言


昨天早上线上系统开始作业了一段时间以后,突然收到服务器报警,服务器CPU持续占用100%,导致线上系统不能正常使用,我登录服务器top了一下,发现java进程占用cpu400%, 由于前天晚上上线了一些新的功能,所以我分析应该是某处代码出现了死循环导致,于是根据前面解决性能问题的经验来搞一下。具体流程参考我前面的博文《快速定位线上CPU100%问题》。


排查结果:

20200709091622497.png

快速找到具体的代码,那么问题就可以很好的解决了,看一下具体代码


20200709091805305.png

public static Map<String, List<String>> mobileAnsweredLineNameMap = new HashedMap();


当看到这的时候有经验的读者可能一眼知道问题了,我看到这个代码的第一反应,这个地方怎么使用了 一个这么单纯的HashMap,这在多线程环境下必死啊。至少我们要是用ConcurrentHashMap,或者 Collections.synchronizedMap(new HashedMap());不能在这裸奔啊。


下面我们分析一下HashMap为什么导致CPU100%


这个问题相关的知识点,有以下几个:


HashMap 的底层数据结构是什么?

什么是哈希碰撞?如何该解决这个问题?

什么是扩展因子?它有什么用?

还有对 HashMap 源码的理解,为什么 HashMap 会导致死循环?


1.HashMap 的底层数据结构


先来说 HashMap 的底层数据结构,看过 HashMap 的源码我们就会发现,JDK 1.7 和 JDK 1.8 HashMap 的组成是不同的,JDK 1.7 HashMap 的组成是数组 + 链表的形式,而 JDK 1.8 新增了红黑树的数据结构,当 HashMap 中的链表长度大于 8 时,链表结构就会转换为红黑树,如下图所示:


aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9Icld3Nlp1WENzaHNTN0JpY1ZQREhNWUloNG1FSXNpY3VlanQ4R29YaEYwdUNPempsT2ZEemdEek1hUXIwN3hmRmZpYzM4aHcySFZVaWJTN2pWWnRkMVo3NVEvNjQw.png2.哈希碰撞及解决方案


所谓的哈希碰撞指的是不同的值,经过哈希之后得到的值确是相同的,这种情况就叫做哈希碰撞或哈希冲突。解决哈希碰撞的常用方法是:开放定址法和链表地址法,而 HashMap 采用的就是链表地址法。它的实现原理就是将 HashMap 中相同的哈希值以链表的形式存储起来。


3.扩展因子


扩展因子也叫加载因子或负载因子是 HashMap 中的一个属性,如下图所示:


aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9Icld3Nlp1WENzaHNTN0JpY1ZQREhNWUloNG1FSXNpY3VlOW5DSlRHSUsybTZFeURHSW1sc3ZWTXRuWmljbDdmZ1RGZWxERlRtb1FVakRRRUYxbms3ZmRxdy82NDA.png

假如数组的默认长度为 10,扩展因子为 0.5,那么当数组超过 10*0.5=5 个时,HashMap 就会扩容为之前容量的两倍,所以说扩展因子就是用来判定 HashMap 是否满足扩容条件的。


4.HashMap死循环分析


HashMap 导致 CPU 100% 的原因就是因为 HashMap 死循环导致的,那 HashMap 是如何造成死循环的?接下来我们一起来看。


以 JDK 1.7 为例,假设 HashMap 的默认大小为 2,HashMap 本身中有一个键值 key(5),我们再使用两个线程:t1 添加 key(3),t2 添加 key(7),首先两个线程先把 key(3) 和 key(7) 都添加到 HashMap 中,此时因为 HashMap 的长度不够用了就会进行扩容操作,然后这时线程 t1 在执行到 Entry<K,V> next = e.next; 时,交出了 CPU 的使用权,源代码如下:

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next; // 线程一执行此处
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

那么此时线程 t1 中的 e 指向了 key(3),而 next 指向了 key(7) ;之后线程 t2 重新 rehash 之后链表的顺序被反转,链表的位置变成了 key(5) -> key(7) -> key(3),其中 “->” 用来表示下一个元素,当 t1 重新获得执行权之后,先执行 newTalbe[i] = e 把 key(3) 的 next 设置为 key(7),而下次循环时查询到 key(7) 的 e.next 为 key(3),于是就形 成了 key(3) 和 key(7) 的环形引用,就导致了死循环的产生,如下图所示:


20200709092927799.png

HashMap 发生死循环的一个重要原因是 JDK 1.7 时链表的插入是首部倒序插入的,而 JDK 1.8 时已经变成了尾部插入,有人把这个死循环的问题反馈给了 Sun 公司,但它们认为这不是一个问题,因为 HashMap 本身就是非线程安全的,如果要在多线程使用建议使用 ConcurrentHashMap 替代 HashMap,但面试中这个问题被问的频率比较高,所以在这里就特殊说明一下。


小结


HashMap 是非线程安全的,以 JDK 1.7 为例,当多线程并发扩容时就会出现环形引用的问题,从而导致死循环的出现,一直死循环就会导致 CPU 运行 100%,所以在多线程使用时,我们需要使用 ConcurrentHashMap 来替代 HashMap。

目录
相关文章
|
3天前
|
Linux
Linux 查看进程PID和线程CPU和内存占用情况
Linux 查看进程PID和线程CPU和内存占用情况
41 0
|
3天前
|
Kubernetes Java 测试技术
ChaosBlade常见问题之在K8s环境下演练cpu满载报错如何解决
ChaosBlade 是一个开源的混沌工程实验工具,旨在通过模拟各种常见的硬件、软件、网络、应用等故障,帮助开发者在测试环境中验证系统的容错和自动恢复能力。以下是关于ChaosBlade的一些常见问题合集:
33 0
|
3天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
71 0
|
3天前
|
缓存 安全 Java
为什么全局变量可能成为多线程环境中的安全隐患
为什么全局变量可能成为多线程环境中的安全隐患
|
7月前
|
SQL Java 数据库连接
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法
135 0
|
2天前
|
消息中间件 SQL Kubernetes
实时计算 Flink版产品使用合集之多线程环境中,遇到 env.addSource 添加数据源后没有执行到 env.execut,是为什么
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
14 1
|
3天前
|
存储 Java Python
【Python小知识】如何解决代理IP在多线程环境下的并发问题?
【Python小知识】如何解决代理IP在多线程环境下的并发问题?
|
3天前
|
Java Shell
java中jvm使用jststak定位线程cpu占用内存高的线程
java中jvm使用jststak定位线程cpu占用内存高的线程
15 5
|
3天前
|
消息中间件 Java 数据库连接
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
45 1
|
3天前
|
缓存 编译器 程序员
C/C++编译器并行优化技术:并行优化针对多核处理器和多线程环境进行优化,以提高程序的并行度
C/C++编译器并行优化技术:并行优化针对多核处理器和多线程环境进行优化,以提高程序的并行度
93 0