HashMap多線程併發問題
HashMap並不是線程安全的,在多個線程put時,會形成key之間的死循環。當另外一個線程調用這個key時,get()方法會一直執行,致使線程積壓,最終形成CPU滿。算法
問題緣由分析
HashMap結構
HashMap經過一個數組table[]來存儲key,當放入一個key時,經過hash算法計算key的下標,並存儲在數組的table[i]處。若是table[]的尺寸很小,當放入多個key時,可能會出現下標相同,這樣就會在table[i]處造成鏈表。這時,本來查找一個key須要耗時O(1),如今耗時變成了O(n),n爲鏈表的長度。數組
所以,爲了提升查找效率,當有新的key值存入時,會檢查Hash表的大小是否超過設定的thredhold,超過的話就會增長Hash表的大小,這樣子Hash表裏的元素會從新計算一遍,這個過程叫作rehash。安全
正常的Rehash過程
假設原先hash的size是2,存放了三個元素a、b、c,都在table[1]這裏,rehash後size爲4。多線程
取出第一個key值a,計算hasn值爲3,存放在table[3];併發
取出第二個key值b,計算hash值爲1,存放在table[1];spa
取出第三個key值c,計算hash值爲3,存放在table[3],與key值a造成鏈表,且c的next指向了a。線程
併發的Rehash過程
若是存在線程1和線程2,在rehash以前中,a、b、c在table[1]造成了鏈表,a的next指向了b,這時發生了put操做,兩個線程同時進行了rehash。get
線程1在遍歷Hash表元素中,取a.next時被掛起。同步
線程2繼續完成了rehash操做,重組了鏈表,重組結束後,b.next指向了a。hash
線程1繼續執行,a.next又指向了b,環形鏈表所以產生了。
這時,當咱們調用到同一鏈表的其餘值時,就會出現死循環,線程一直會執行下去。
解決方案
HashTable
HashTable是同步的,對此對HashTable進行操做時,都會鎖住整個結構。若是併發地修改,會拋出異常。
HashTable不支持在遍歷時修改自身的元素,不然會拋出ConcurrentModificationException。
ConcurrentHashMap
ConcurrentHashMap的鎖是細粒度的,將hash表分爲16個桶(默認),每次須要時只會鎖當前用到的桶。並且在讀是不會鎖表,徹底支持併發操做。只有在size()時會鎖住整個表,所以ConcurrentHashMap併發時效率更高。
此外,ConcurrentHashMap則是在遍歷迭代時發生改變不會拋出異常。