今天想知道HashMap為什么在多線程下不安全,找了許多資料,終於理解了。
首先先了解一下HashMap:
HashMap實現的原理是:數組+鏈表
HashMap
的size大於等於(容量*加載因子)的時候,會觸發擴容的操作,這個是個代價不小的操作。
為什么要擴容呢?
HashMap
默認的容量是16,隨着元素不斷添加到HashMap
里,出現hash
沖突的機率就更高,那每個桶對應的鏈表就會更長,
這樣會影響查詢的性能,因為每次都需要遍歷鏈表,比較對象是否相等,一直到找到元素為止。
為了提升查詢性能,只能擴容,減少hash
沖突,讓元素的key
盡量均勻的分布。
在單線程中,HashMap是安全的,但是在高並發的環境下,會出現不安全,原因在於HashMap的擴容。
我們先看下HashMap擴容的代碼:
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable);//可能導致環鏈 table = newTable; threshold = (int)(newCapacity * loadFactor); }
transfer方法就是進行HashMap的擴容的核心方法:
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
在並發情況下進行擴容,有一個線程執行到
Entry<K,V> next = e.next;
而另外一個線程已經執行完擴容,再等這個線程執行完就會出現環路,並且也會丟失一些節點。
我查看一下陳皓大神的文章,里面寫的很詳細:
https://coolshell.cn/articles/9606.html
正常的ReHash的過程
畫了個圖做了個演示。
- 我假設了我們的hash算法就是簡單的用key mod 一下表的大小(也就是數組的長度)。
- 最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都沖突在table[1]這里了。
- 接下來的三個步驟是Hash表 resize成4,然后所有的<key,value> 重新rehash的過程
並發下的Rehash
1)假設我們有兩個線程。我用紅色和淺藍色標注了一下。
我們再回頭看一下我們的 transfer代碼中的這個細節:
1
2
3
4
5
6
7
|
do
{
Entry<K,V> next = e.next;
// <--假設線程一執行到這里就被調度掛起了
int
i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
while
(e !=
null
);
|
而我們的線程二執行完成了。於是我們有下面的這個樣子。
注意,因為Thread1的 e 指向了key(3),而next指向了key(7),其在線程二rehash后,指向了線程二重組后的鏈表。我們可以看到鏈表的順序被反轉后。
2)線程一被調度回來執行。
- 先是執行 newTalbe[i] = e;
- 然后是e = next,導致了e指向了key(7),
- 而下一次循環的next = e.next導致了next指向了key(3)
3)一切安好。
線程一接着工作。把key(7)摘下來,放到newTable[i]的第一個,然后把e和next往下移。
4)環形鏈接出現。
e.next = newTable[i] 導致 key(3).next 指向了 key(7)
注意:此時的key(7).next 已經指向了key(3), 環形鏈表就這樣出現了。
於是,當我們的線程一調用到,HashTable.get(11)時,悲劇就出現了——Infinite Loop。