• java中HashMap在多线程环境下引起CPU100%的问题解决


     最近项目中出现了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。

    可能出现问题的地方是在扩容的时候

    Java代码  收藏代码
    1. void resize(int newCapacity) {    
    2.         Entry[] oldTable = table;    
    3.         int oldCapacity = oldTable.length;    
    4.         if (oldCapacity == MAXIMUM_CAPACITY) {    
    5.             threshold = Integer.MAX_VALUE;    
    6.             return;    
    7.         }    
    8.     
    9.         Entry[] newTable = new Entry[newCapacity];    
    10.         transfer(newTable);    
    11.         table = newTable;    
    12.         threshold = (int)(newCapacity * loadFactor);    
    13.     }    

     这个方法本身没有问题,问题出在transfer(newTable);这个方法是用来移动oldTable里的数据到newTable里。

    Java代码  收藏代码
    1. /**  
    2.     * Transfers all entries from current table to newTable.  
    3.     */    
    4.    void transfer(Entry[] newTable) {    
    5.        Entry[] src = table;    
    6.        int newCapacity = newTable.length;    
    7.        for (int j = 0; j < src.length; j++) {    
    8.         //(1)    
    9.            Entry<K,V> e = src[j];    
    10.            if (e != null) {    
    11.                src[j] = null;    
    12.                do {    
    13.                 //(2)    
    14.                    Entry<K,V> next = e.next;    
    15.                    int i = indexFor(e.hash, newCapacity);    
    16.                 //(3)    
    17.                    e.next = newTable[i];    
    18.                    newTable[i] = e;    
    19.                    e = next;    
    20.                } while (e != null);    
    21.            }    
    22.        }    
    23.    }    

     下面分析可能出现的情况,假设原来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。

  • 相关阅读:
    SQL Server 2005 Integration Services (SSIS)数据源之MySQL
    SQL Server 2005 Integration Services (SSIS)数据源之Sybase
    oracle10g 监听服务无法启动
    创建一个People类型,有年龄、工资、性别三个属性。 定义一个方法叫做找对象,找对象方法传过来一个人;
    基于组件的C#农历算法
    ASP.NET中实现中文简/繁体自动转换的类
    最牛的QQ资料
    单一登陆(Single Signon)问题
    Smart Client Case Study Source Code Download from MSDN China
    简历制作 | 论文资源 | 考试认证 | 招聘求职 | 文学艺术 | 外语学习
  • 原文地址:https://www.cnblogs.com/gisblogs/p/4235948.html
Copyright © 2020-2023  润新知