當前位置:編程學習大全網 - 源碼下載 - hashmap為什麽是線程不安全的

hashmap為什麽是線程不安全的

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

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

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

擴容造成死循環和數據丟失的分析過程

假設現在有兩個線程A、B同時對下面這個HashMap進行擴容操作:

正常擴容後的結果是下面這樣的:

但是當線程A執行到上面transfer函數的第11行代碼時,CPU時間片耗盡,線程A被掛起。即如下圖中位置所示:

此時線程A中:e=3、next=7、e.next=null

當線程A的時間片耗盡後,CPU開始執行線程B,並在線程B中成功的完成了數據遷移

重點來了,根據Java內存模式可知,線程B執行完數據遷移後,此時主內存中newTable和table都是最新的,也就是說:7.next=3、3.next=null。

隨後線程A獲得CPU時間片繼續執行newTable[i] = e,將3放入新數組對應的位置,執行完此輪循環後線程A的情況如下:

接著繼續執行下壹輪循環,此時e=7,從主內存中讀取e.next時發現主內存中7.next=3,於是乎next=3,並將7采用頭插法的方式放入新數組中,並繼續執行完此輪循環,結果如下:

執行下壹次循環可以發現,next=e.next=null,所以此輪循環將會是最後壹輪循環。接下來當執行完e.next=newTable[i]即3.next=7後,3和7之間就相互連接了,當執行完newTable[i]=e後,3被頭插法重新插入到鏈表中,執行結果如下圖所示:

上面說了此時e.next=null即next=null,當執行完e=null後,將不會進行下壹輪循環。到此線程A、B的擴容操作完成,很明顯當線程A執行完後,HashMap中出現了環形結構,當在以後對該HashMap進行操作時會出現死循環。

並且從上圖可以發現,元素5在擴容期間被莫名的丟失了,這就發生了數據丟失的問題。

JDK1.8中的線程不安全

根據上面JDK1.7出現的問題,在JDK1.8中已經得到了很好的解決,如果妳去閱讀1.8的源碼會發現找不到transfer函數,因為JDK1.8直接在resize函數中完成了數據遷移。另外說壹句,JDK1.8在進行元素插入時使用的是尾插法。

為什麽說JDK1.8會出現數據覆蓋的情況喃,我們來看壹下下面這段JDK1.8中的put操作代碼:

其中第六行代碼是判斷是否出現hash碰撞,假設兩個線程A、B都在進行put操作,並且hash函數計算出的插入下標是相同的,當線程A執行完第六行代碼後由於時間片耗盡導致被掛起,而線程B得到時間片後在該下標處插入了元素,完成了正常的插入,然後線程A獲得時間片,由於之前已經進行了hash碰撞的判斷,所有此時不會再進行判斷,而是直接進行插入,這就導致了線程B插入的數據被線程A覆蓋了,從而線程不安全。

除此之前,還有就是代碼的第38行處有個++size,我們這樣想,還是線程A、B,這兩個線程同時進行put操作時,假設當前HashMap的zise大小為10,當線程A執行到第38行代碼時,從主內存中獲得size的值為10後準備進行+1操作,但是由於時間片耗盡只好讓出CPU,線程B快樂的拿到CPU還是從主內存中拿到size的值10進行+1操作,完成了put操作並將size=11寫回主內存,然後線程A再次拿到CPU並繼續執行(此時size的值仍為10),當執行完put操作後,還是將size=11寫回內存,此時,線程A、B都執行了壹次put操作,但是size的值只增加了1,所有說還是由於數據覆蓋又導致了線程不安全。

總結

HashMap的線程不安全主要體現在下面兩個方面:

1.在JDK1.7中,當並發執行擴容操作時會造成環形鏈和數據丟失的情況。

2.在JDK1.8中,在並發執行put操作時會發生數據覆蓋的情況。

轉自:

網頁鏈接

  • 上一篇:求C語言編程文件(課程設計)源代碼和程序框的劃分和組合
  • 下一篇:人力資源主管簡歷範文3篇
  • copyright 2024編程學習大全網