HashMap其實並非線程安全的,在高併發的狀況下,會產生併發引發的問題:
好比:java
下面逐個分析下出現上述狀況的緣由:安全
HashMap進行存儲時,若是size超過(當前最大容量*負載因子)時候會發生resize,首先看一下resize源代碼:多線程
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; // transfer方法是真正執行rehash的操做,容易在高併發時發生問題 transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
而這段代碼中又調用了transfer()方法,而這個方法實現的機制就是將每一個鏈表轉化到新鏈表,而且鏈表中的位置發生反轉,而這在多線程狀況下是很容易形成鏈表迴路,從而發生死循環,咱們看一下他的源代碼:併發
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
HashMap死循環演示:
假若有兩個線程P一、P2,以及table[]某個節點鏈表爲 a->b->null(a、b是HashMap的Entry節點,保存着Key-Value鍵值對的值)ide
P1先執行,執行完"Entry<K,V> next = e.next;"代碼後,P1發生阻塞或者其餘狀況再也不執行下去,此時e=a,next=b高併發
transfer(newTable); //P1阻塞在transfer方法中,沒有執行到下邊對 table 和 threshold 從新賦值的操做 table = newTable; threshold = (int)(newCapacity * loadFactor);
一個線程利用迭代器迭代時,另外一個線程作插入刪除操做,形成迭代的fast-fail。ui
public class TestFailFast { private static final String USER_NAME_PREFIX = "User-"; // Key: User Name, Value: User Age private static Map<String, Integer> userMap = new HashMap<>(); // ThreadA 用於向HashMap添加元素 static class ThreadA implements Runnable { @Override public void run() { System.out.println("ThreadA starts to add user."); for (int i = 1; i < 100000; i++) { userMap.put(USER_NAME_PREFIX+i, i%100); } System.out.println("ThreadA done."); } } // ThreadB 用於遍歷HashMap中元素輸出 static class ThreadB implements Runnable { @Override public void run() { System.out.println("ThreadB starts to iterate."); for (Map.Entry<String, Integer> user : userMap.entrySet()) { System.out.println("UserName=" + user.getKey() + ", UserAge=" + user.getValue()); } System.out.println("ThreadB done."); } } public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(new ThreadA()); Thread threadB = new Thread(new ThreadB()); threadA.start(); threadB.start(); threadA.join(); threadB.join(); System.exit(0); } }
運行結果:拋出ConcurrentModificationException.net
ThreadA starts to add user. ThreadB starts to iterate. Exception in thread "Thread-1" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437) at java.util.HashMap$EntryIterator.next(HashMap.java:1471) at java.util.HashMap$EntryIterator.next(HashMap.java:1469) at concurrent.TestFailFast$ThreadB.run(TestFailFast.java:33) at java.lang.Thread.run(Thread.java:748) ThreadA done.
HashMap並不是線程安全,因此在多線程狀況下,應該首先考慮用ConcurrentHashMap,避免悲劇的發生。線程
https://blog.csdn.net/chenxuegui1234/article/details/39646041
https://blog.csdn.net/u011716215/article/details/78601916code