天天看點

HashMap線程不安全問題

一、問題現象

        虛拟機建立失敗後,發現底層(openstack)的異常虛拟機還在,沒有做删除復原。檢視日志發現“java.util.ConcurrentModificationException”異常:

HashMap線程不安全問題

二、問題分析

        建立虛拟機失敗後,會下發指令删除虛拟機做復原,而删除前會查詢虛拟機,由于查詢虛拟機很慢,起了多線程分别查計算、存儲、網絡等相關資訊。而查詢時都用到了同一個HashMap對象,這時并行操作,出現了讀寫時并發異常。問題出現是小機率事件,并不必現。

三、簡化代碼,抽象問題

        為了友善分析,簡化代碼,抽象出兩個類(實際版本中代碼比這個複雜很多),一個類為Request.java,如下:

HashMap線程不安全問題

       另一個是查詢虛拟機資訊的多線程類,QueryVmInfo,代碼如下:

HashMap線程不安全問題
HashMap線程不安全問題

        多次執行QueryVmInfo,會出現“java.util.ConcurrentModificationException”異常。原因是由于HashMap非線程安全,多線程并行去讀/寫map中的值時出現沖突。

四、解決辦法

        直接将HashMap改為ConcurrentHashMap即可。但必須得注意HashMap的key值可以有一個為null,value值可以多個為null。而ConcurrentHashMap是不行的。在版本中,我直接将HashMap改為ConcurrentHashMap送出到正式版本中了,結果晚上1點,接到公司電話,改的這個地方引出問題了。因為Request是公用方法,調用的地方          太多,有些地方并沒有判空就往map中塞值,而ConcurrentHashMap又不支援,是以直接報錯了。其實修改方法可以調用的地方的,但由于剛好在出版本,而調用的地方太多,最後改了公用方法,put時判空,不為空才讓put。

五、引申

        将HashMap改為ConcurrentHashMap後,其實代碼還是有問題的,且問題很明顯,因為并發查虛拟機的計算、存儲、網絡時,用的是同一個Request的同一個map,而代碼本意是先對map進行clear,然後再塞值,最後再用自己所塞的值,3個操作本應是一個原子操作,但實際上由于并發并沒有控制好,這樣就會出現各種各樣讀髒資料的問         題,這也是多次運作程式會發現結果五花八門的原因。修改方法可以在Request中,封裝一個克隆方法,并行操作時克隆一個。