多線程下,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進程id3、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(當然它也不是線程安全的)。