轉載請聲明出處!數組
在多線程環境下,使用HashMap進行put操做會引發死循環,致使CPU利用率接近100%,HashMap在併發執行put操做時會引發死循環,是由於多線程會致使HashMap的Entry鏈表安全
造成環形數據結構,一旦造成環形數據結構,Entry的next節點永遠不爲空,就會產生死循環獲取Entry。那麼這個死循環是如何生成的呢?咱們來仔細分析下。數據結構
引起死循環,是在HashMap的擴容操做中,正常的擴容操做是這個流程。HashMap的擴容在put操做中會觸發擴容,主要是三個方法:多線程
綜合來講,HashMap一次擴容的過程:併發
一、取當前table的2倍做爲新table的大小操作系統
二、根據算出的新table的大小new出一個新的Entry數組來,名爲newTable線程
三、輪詢原table的每個位置,將每一個位置上鍊接的Entry,算出在新table上的位置,並以鏈表形式鏈接3d
四、原table上的全部Entry所有輪詢完畢以後,意味着原table上面的全部Entry已經移到了新的table上,HashMap中的table指向newTablecdn
如今hashmap中有三個元素,Hash表的size=2, 因此key = 3, 7, 5,在mod 2之後都衝突在table[1]這裏了。blog
按照方法中的代碼
對table[1]中的鏈表來講,進入while循環,此時e=key(3),那麼next=key(7),通過計算從新定位e=key(3)在新表中的位置,並把e=key(3)掛在newTable[3]的位置
這樣循環下去,將table[1]中的鏈表循環完成後,因而HashMap就完成了擴容
上面都是單線程下的擴容,當多線程進行擴容時,會是什麼樣子呢?
初始的HashMap仍是:
咱們如今假設有兩個線程併發操做,都進入了擴容操做, 咱們以顏色進行區分兩個線程。
回顧咱們的擴容代碼,咱們假設,線程1執行到Entry<K,V> next = e.next;時被操做系統調度掛起了,而線程2執行完成了擴容操做
因而,在線程1,2看來,就應該是這個樣子
接下來,線程1被調度回來執行:
1)
2)
3)
4)
5)
6)
7)
循環列表產生後,一旦線程1調用get(11,15之類的元素)時,就會進入一個死循環的狀況,將CPU的消耗到100%。
HashMap之因此在併發下的擴容形成死循環,是由於,多個線程併發進行時,由於一個線程先期完成了擴容,將原的鏈表從新散列到本身的表中,而且鏈表變成了倒序,後一個線程再擴容時,又進行本身的散列,再次將倒序鏈表變爲正序鏈表。因而造成了一個環形鏈表,當表中不存在的元素時,形成死循環。
雖然在JDK1.8中,Java的開發小組修正了這個問題,可是HashMap始終存在着其餘的線程安全問題。因此在併發狀況下,咱們應該使用HastTable或者ConcurrentHashMap來代替HashMap。
你的贊和關注是我繼續創做的動力~