hashmap的原理以及如何實現,之前在 jdk7與jdk8中hashmap的實現 中已經說明了。
那麼,為什麼說hashmap是執行緒不安全的呢?它在多執行緒環境下,會發生什麼情況呢?
1. resize死迴圈
我們都知道hashmap初始容量大小為16,一般來說,當有資料要插入時,都會檢查容量有沒有超過設定的thredhold,如果超過,需要增大hash表的尺寸,但是這樣一來,整個hash表裡的元素都需要被重算一遍。這叫rehash,這個成本相當的大。
void resize(int newcapacity)
entry newtable = new entry[newcapacity];
transfer(newtable, inithashseedasneeded(newcapacity));
table = newtable;
threshold = (int)math.min(newcapacity * loadfactor, maximum_capacity + 1);}
複製**
void transfer(entry newtable, boolean rehash)
int i = indexfor(e.hash, newcapacity);
e.next = newtable[i];
newtable[i] = e;
e = next;}}
} 複製**
大概看下transfer:
對索引陣列中的元素遍歷
對鍊錶上的每乙個節點遍歷:用 next 取得要轉移那個元素的下乙個,將 e 轉移到新 hash 表的頭部,使用頭插法插入節點。
迴圈2,直到鍊錶節點全部轉移
迴圈1,直到所有索引陣列全部轉移
經過這幾步,我們會發現轉移的時候是逆序的。假如轉移前鍊錶順序是1->2->3,那麼轉移後就會變成3->2->1。這時候就有點頭緒了,死鎖問題不就是因為1->2的同時2->1造成的嗎?所以,hashmap 的死鎖問題就出在這個 transfer() 函式上。
1.1 單執行緒 rehash 詳細演示
單執行緒情況下,rehash 不會出現任何問題:
假設hash演算法就是最簡單的 key mod table.length(也就是陣列的長度)。
最上面的是old hash 表,其中的hash表的 size = 2, 所以 key = 3, 7, 5,在 mod 2以後碰撞發生在 table[1]
接下來的三個步驟是 hash表 resize 到4,並將所有的 重新rehash到新 hash 表的過程
如圖所示:
1.2 多執行緒 rehash 詳細演示
為了思路更清晰,我們只將關鍵**展示出來
while(null != e)
複製**
entry next = e.next; ——因為是單鏈表,如果要轉移頭指標,一定要儲存下乙個結點,不然轉移後鍊錶就丟了 e.next = newtable
; ——e 要插入到鍊錶的頭部,所以要先用 e.next 指向新的 hash 表第乙個元素(為什麼不加到新鍊錶最後?因為複雜度是 o(n)) newtable= e; ——現在新 hash 表的頭指標仍然指向 e 沒轉移前的第乙個元素,所以需要將新 hash 表的頭指標指向 e e = next ——轉移 e 的下乙個結點
假設這裡有兩個執行緒同時執行了 put() 操作,並進入了 transfer() 環節
while(null != e)
複製**
那麼現在的狀態為:
然後執行緒1被喚醒了:北京整容醫院
執行 e.next = newtable
,於是 key(3)的 next 指向了執行緒1的新 hash 表,因為新 hash 表為空,所以 e.next = null , 執行 newtable= e ,所以執行緒1的新 hash 表第乙個元素指向了執行緒2新 hash 表的 key(3)。好了,e 處理完畢。 執行 e = next ,將 e 指向 next,所以新的 e 是 key(7)
然後該執行 key(3)的 next 節點 key(7)了:
現在的 e 節點是 key(7),首先執行 entry next = e.next ,那麼 next 就是 key(3)了 執行 e.next = newtable
,於是key(7) 的 next 就成了 key(3) 執行 newtable= e ,那麼執行緒1的新 hash 表第乙個元素變成了 key(7) 執行 e = next ,將 e 指向 next,所以新的 e 是 key(3)
這時候的狀態圖為:
然後又該執行 key(7)的 next 節點 key(3)了:
現在的 e 節點是 key(3),首先執行 entry next = e.next ,那麼 next 就是 null 執行 e.next = newtable
,於是key(3) 的 next 就成了 key(7) 執行 newtable= e ,那麼執行緒1的新 hash 表第乙個元素變成了 key(3) 執行 e = next ,將 e 指向 next,所以新的 e 是 key(7)
這時候的狀態如圖所示:北京藝星整形醫院
很明顯,環形鍊錶出現了!!當然,現在還沒有事情,因為下乙個節點是 null,所以 transfer() 就完成了,等 put() 的其餘過程搞定後,hashmap 的底層實現就是執行緒1的新 hash 表了。
2. fail-fast
如果在使用迭代器的過程中有其他執行緒修改了map,那麼將丟擲concurrentmodificationexception,這就是所謂fail-fast策略。
這個異常意在提醒開發者及早意識到執行緒安全問題。
談談HashMap執行緒不安全的體現
hashmap的原理以及如何實現,之前在jdk7與jdk8中hashmap的實現中已經說明了。那麼,為什麼說hashmap是執行緒不安全的呢?它在多執行緒環境下,會發生什麼情況呢?resize死迴圈 我們都知道hashmap初始容量大小為16,一般來說,當有資料要插入時,都會檢查容量有沒有超過設定的...
hashMap的執行緒不安全
hashmap是非執行緒安全的,表現在兩種情況下 1 擴容 t1執行緒對map進行擴容,此時t2執行緒來讀取資料,原本要讀取位置為2的元素,擴容後此元素位置未必是2,則出現讀取錯誤資料。2 hash碰撞 兩個執行緒新增元素發生hash碰撞,都要將此元素新增到鍊錶的頭部,則會發生資料被覆蓋。詳情 ha...
HashMap 執行緒不安全的原因
hashmap執行緒安全的問題,在各大面試中都會被問到,屬於常考熱點題目。雖然大部分讀者都了解它不是執行緒安全的,但是再深入一些,問它為什麼不是執行緒安全的,仔細說說原理,用圖畫出一種非執行緒安全的情況?1.8之後又做了什麼改善了這點?很多讀者可能一時想不出很好的答案。我們關注下面的 void tr...