談談HashMap執行緒不安全的體現

2021-09-11 11:17:20 字數 2860 閱讀 5147

hashmap的原理以及如何實現,之前在jdk7與jdk8中hashmap的實現中已經說明了。

那麼,為什麼說hashmap是執行緒不安全的呢?它在多執行緒環境下,會發生什麼情況呢?

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)
entrynext = e.next;——因為是單鏈表,如果要轉移頭指標,一定要儲存下乙個結點,不然轉移後鍊錶就丟了

e.next = newtable[i];——e 要插入到鍊錶的頭部,所以要先用 e.next 指向新的 hash 表第乙個元素(為什麼不加到新鍊錶最後?因為複雜度是 o(n))

newtable[i] = e;——現在新 hash 表的頭指標仍然指向 e 沒轉移前的第乙個元素,所以需要將新 hash 表的頭指標指向 e

e = next——轉移 e 的下乙個結點

假設這裡有兩個執行緒同時執行了put()操作,並進入了transfer()環節

while(null != e)
然後執行緒1被喚醒了:

執行e.next = newtable[i],於是 key(3)的 next 指向了執行緒1的新 hash 表,因為新 hash 表為空,所以e.next = null,

執行newtable[i] = e,所以執行緒1的新 hash 表第乙個元素指向了執行緒2新 hash 表的 key(3)。好了,e 處理完畢。

執行e = next,將 e 指向 next,所以新的 e 是 key(7)

然後該執行 key(3)的 next 節點 key(7)了:

現在的 e 節點是 key(7),首先執行entrynext = e.next,那麼 next 就是 key(3)了

執行e.next = newtable[i],於是key(7) 的 next 就成了 key(3)

執行newtable[i] = e,那麼執行緒1的新 hash 表第乙個元素變成了 key(7)

執行e = next,將 e 指向 next,所以新的 e 是 key(3)

這時候的狀態圖為:

然後又該執行 key(7)的 next 節點 key(3)了:

現在的 e 節點是 key(3),首先執行entrynext = e.next,那麼 next 就是 null

執行e.next = newtable[i],於是key(3) 的 next 就成了 key(7)

執行newtable[i] = e,那麼執行緒1的新 hash 表第乙個元素變成了 key(3)

執行e = next,將 e 指向 next,所以新的 e 是 key(7)

這時候的狀態如圖所示:

很明顯,環形鍊錶出現了!!當然,現在還沒有事情,因為下乙個節點是 null,所以transfer()就完成了,等put()的其餘過程搞定後,hashmap 的底層實現就是執行緒1的新 hash 表了。

原文出處:談談hashmap執行緒不安全的體現

談談HashMap執行緒不安全的體現

hashmap的原理以及如何實現,之前在 jdk7與jdk8中hashmap的實現 中已經說明了。那麼,為什麼說hashmap是執行緒不安全的呢?它在多執行緒環境下,會發生什麼情況呢?1.resize死迴圈 我們都知道hashmap初始容量大小為16,一般來說,當有資料要插入時,都會檢查容量有沒有超...

hashMap的執行緒不安全

hashmap是非執行緒安全的,表現在兩種情況下 1 擴容 t1執行緒對map進行擴容,此時t2執行緒來讀取資料,原本要讀取位置為2的元素,擴容後此元素位置未必是2,則出現讀取錯誤資料。2 hash碰撞 兩個執行緒新增元素發生hash碰撞,都要將此元素新增到鍊錶的頭部,則會發生資料被覆蓋。詳情 ha...

HashMap 執行緒不安全的原因

hashmap執行緒安全的問題,在各大面試中都會被問到,屬於常考熱點題目。雖然大部分讀者都了解它不是執行緒安全的,但是再深入一些,問它為什麼不是執行緒安全的,仔細說說原理,用圖畫出一種非執行緒安全的情況?1.8之後又做了什麼改善了這點?很多讀者可能一時想不出很好的答案。我們關注下面的 void tr...