性能分析5~top命令、jps命令、jstack命令:分析多線程下HashMap卡死原因分析

多線程下,HashMap使用很容易出bug,直接被卡死,原因分析我們先看一段代碼:

import java.util.HashMap;
import java.util.Map;

/**
 * 多線程下HashMap卡死原因分析
 */
public class Test4_HashMap_bug {
    
    static Map<String, String> map = new HashMap<String, String>();
    
    public static class AddThread implements Runnable {
        
        int start = 0;
        
        public AddThread(int start){
            this.start = start;
        }

        @Override
        public void run() {
            for(int i=start;i<10000;i+=2){
                map.put(Integer.toString(i), Integer.toBinaryString(i));
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(new AddThread(0));
        Thread t2 = new Thread(new AddThread(1));
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(map.size());
    }
}
我們期待的結果是輸出20000,或者因爲線程不安全輸出一個比20000小的數字也行,但實際情況是運行一直無法結束,直接被卡死了,而且cpu的使用率在不斷的飆升,我們一步步分析原因:

1、top命令查看cpu的使用情況

PID    COMMAND      %CPU  TIME     #TH   #WQ  #PORT MEM    PURG   CMPRS  PGRP  PPID  STATE    BOOSTS           %CPU_ME
10889  top          1.9   00:00.83 1/1   0    20    2968K  0B     0B     10889 10869 running  *0[1]            0.00000
10886  java         197.8 00:57.72 22/2  1    82    12M    0B     0B     8892  8892  running  *0[1]            0.00000
10878  mdworker     0.0   00:00.07 4     1    48    10M    0B     0B     10878 1     sleeping *0[1]            0.00000
10876  ocspd        0.0   00:00.02 2     1    32    1968K  0B     0B     10876 1     sleeping *0[1]            0.00000
10869  bash         0.0   00:00.04 1     0    16    916K   0B     0B     10869 10868 sleeping *0[1]            0.00000
10868  login        0.0   00:00.02 2     1    28    1180K  0B     0B     10868 259   sleeping *0[9]            0.00000
10745  quicklookd   0.0   00:00.09 4     1    86    4572K  32K    0B     10745 1     sleeping  0[0]            0.00000
我們發現pid爲10886的進程cpu使用率很高

2、jps查詢有問題的進程id

zhengchao1991deMacBook-Pro:~ zhengchao1991$ top
zhengchao1991deMacBook-Pro:~ zhengchao1991$ jps
8892 
10895 Jps
10886 Test4_HashMap_bug
此命令更容易定位java進程id

3、jstack查詢有問題進程的內部線程運行情況

zhengchao1991deMacBook-Pro:~ zhengchao1991$ jstack 10886
2017-09-13 14:04:12
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode):

"Attach Listener" daemon prio=5 tid=0x00007fbb8486e800 nid=0x1407 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" prio=5 tid=0x00007fbb84865800 nid=0x5503 runnable [0x000070000877c000]
   java.lang.Thread.State: RUNNABLE
    at java.util.HashMap.put(HashMap.java:494)
    at Test4_HashMap_bug$AddThread.run(Test4_HashMap_bug.java:21)
    at java.lang.Thread.run(Thread.java:745)

"Thread-0" prio=5 tid=0x00007fbb84865000 nid=0x5303 runnable [0x0000700008679000]
   java.lang.Thread.State: RUNNABLE
    at java.util.HashMap.put(HashMap.java:494)
    at Test4_HashMap_bug$AddThread.run(Test4_HashMap_bug.java:21)
    at java.lang.Thread.run(Thread.java:745)

"Service Thread" daemon prio=5 tid=0x00007fbb8701b000 nid=0x4f03 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=5 tid=0x00007fbb8701a000 nid=0x4d03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=5 tid=0x00007fbb87018800 nid=0x4b03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=5 tid=0x00007fbb87009800 nid=0x4903 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=5 tid=0x00007fbb87003800 nid=0x3903 in Object.wait() [0x0000700008067000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007aaa84858> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    - locked <0x00000007aaa84858> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" daemon prio=5 tid=0x00007fbb87003000 nid=0x3703 in Object.wait() [0x0000700007f64000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007aaa84470> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x00000007aaa84470> (a java.lang.ref.Reference$Lock)

"main" prio=5 tid=0x00007fbb85805800 nid=0x1c03 in Object.wait() [0x0000700007546000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007aaadd9a0> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1281)
    - locked <0x00000007aaadd9a0> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1355)
    at Test4_HashMap_bug.main(Test4_HashMap_bug.java:31)
我們看到main方法一直在等待線程的執行完成,join方法,說明我們的線程一直沒有執行完成,而且造成了cpu的使用率飆升,之前的文章中,我們也說過導致cpu飆升的原因之一就是代碼中有死循環這個因素。


4、分析hashmap的put方法的源碼

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}


 for (Entry<K,V> e = table[i]; e != null; e = e.next) {


5、原因分析

根據這段代碼可以看到,當前這兩個線程正在遍歷HashMap的內部數據;
當前所處循環乍看之下是一個迭代遍歷,就如同遍歷一個鏈表一樣;
但在此時此刻,由於多線程的衝突,這個鏈表的結構已經遭到了破壞,鏈表成環了,


上述的迭代就等同於一個死循環,

由此造成main方法一直無法執行完成,也造成了cpu的飆升;

此問題已經在jdk8中修復,如果你用的不是jdk8,可以使用ConcurrentHashMap來代替HashMap(當然它也不是線程安全的)。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章