JDK1.7和JDK1.8中HashMap為什么是線程不安全的?


https://blog.csdn.net/swpu_ocean/article/details/88917958

HashMap的線程不安全體現在會造成死循環、數據丟失、數據覆蓋這些問題。其中死循環和數據丟失是在JDK1.7中出現的問題,在JDK1.8中已經得到解決,然而1.8中仍會有數據覆蓋這樣的問題。

擴容引發的線程不安全

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // 如果之前的HashMap已經擴充打最大了,那么就將臨界值threshold設置為最大的int值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        // 根據新傳入的newCapacity創建新Entry數組
        Entry[] newTable = new Entry[newCapacity];
        // 用來將原先table的元素全部移到newTable里面,重新計算hash,然后再重新根據hash分配位置
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        // 再將newTable賦值給table
        table = newTable;
        // 重新計算臨界值,擴容公式在這兒(newCapacity * loadFactor)
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

HashMap的線程不安全主要是發生在擴容函數中,即根源是在transfer函數中,JDK1.7中HashMaptransfer函數如下:

 1 void transfer(Entry[] newTable, boolean rehash) {
 2         int newCapacity = newTable.length;
 3         for (Entry<K,V> e : table) {
 4             while(null != e) {
 5                 Entry<K,V> next = e.next;
 6                 if (rehash) {
 7                     e.hash = null == e.key ? 0 : hash(e.key);
 8                 }
 9                 int i = indexFor(e.hash, newCapacity);
10                 e.next = newTable[i];
11                 newTable[i] = e;
12                 e = next;
13             }
14         }
15     }

這段代碼是HashMap的擴容操作,重新定位每個桶的下標,並采用頭插法將元素遷移到新數組中。頭插法會將鏈表的順序翻轉,這也是形成死循環的關鍵點。理解了頭插法后再繼續往下看是如何造成死循環以及數據丟失的。

擴容造成死循環和數據丟失的分析過程
假設現在有兩個線程A、B同時對下面這個HashMap進行擴容操作:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM