• 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。

    http://shuaijie506.iteye.com/blog/1815213

     http://blog.163.com/huxb23%40126/blog/static/625898182011211318854/

  • 相关阅读:
    摘抄博文
    html5中getContext()方法和CanvasRenderingContext2D对象详解
    三十四、Android Spinner控件之键值对用法
    二十七、Android之GPS定位详解
    J2EE用监听器实现同一用户只能有一个在线
    三十二、Android上传文件至服务器
    二十六、Android WebView缓存
    三十九、Android原理 不需要关闭后台运行程序
    三十七、android sqlite3详解
    struts2之注解权限控制
  • 原文地址:https://www.cnblogs.com/softidea/p/4376962.html
Copyright © 2020-2023  润新知