一個異常引發的對Hashtable和HashMap的思考
對於Hashtable和HashMap,相信每個學習Java的人都不會陌生,這兩個集合在用法上並沒有什麼不同,但在使用環境上卻有很大差別:
(1)區別,這兩個類主要有以下幾方面的不同: Hashtable和HashMap都實現了Map介面,但是Hashtable的實現是基於Dictionary抽象類 在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。 當get()方法返回null值時,即可以表示
HashMap中沒有該鍵,也可以表示該鍵所對應的值為null。
因此,在HashMap中不能由get()方法來判斷HashMap
上面所提的是面試官非常熱衷提問的(當然有水平的公司也會問的更深,比如HashMap的實現機制),但這並非今天要說的重點。
本人學習Java語言多年,自認對這兩個類瞭如指掌,可是一次變成過程中所遇到的異常,讓我發現自己對這兩個類的理解還不夠深入。
前段時間,本人使用Java語言實現了一個時間排程演算法,這個演算法是本人一片論文中的,該演算法涉及多執行緒,所以我理所應當的用到了HashTable,但在測試階段卻頻繁的跳出一個異常,ConcurrentModificationException,雖然很快得到了很好的解決,但這個異常,使我對HashMap,Hashtable,ConcurrentHashMap有了一個更深層的認識。
首先,很多人都知道,多執行緒情況下應該使用HashTable,或是使用Collections中的SynchronizedMap對HashMap進行同步。但看似同步的Map卻仍然存在潛在的執行緒安全問題,可以考慮下面一種場景
如下面這段程式碼:Java程式碼
<pre class="java" name="code"><span style="font-size:18px;">// shm是SynchronizedMap的一個例項 if(shm.containsKey('key')){ shm.remove(key); } </span>
這段程式碼用於從map中刪除一個元素之前判斷是否存在這個元素。這裡的containsKey和reomve方法都是同步的,但是整段程式碼卻不是。為什麼?假設:執行緒A執行了containsKey方法返回true,準備執行remove操作;這時另一個執行緒B開始執行,同樣執行了containsKey方法返回true,並接著執行了remove操作;然後執行緒A接著執行remove操作時發現此時已經沒有這個元素了。這時就會丟擲上面我所遇到的異常ConcurrentModificationException,要保證這段程式碼按我們的意願工作,一個辦法就是對這段程式碼進行同步控制,但是這麼做付出的代價太大。
我演算法中所遇到的異常產生於迭代過程中,程式碼如下:
<span style="font-size:18px;"> Iterator keys = map.keySet().iterator();
while(keys.hasNext()){
map.get(keys.next());
}
</span>
碰到這個異常的時候我也百思不解,明明是執行緒安全的怎麼會產生這個異常,原來得到的keySet和迭代器都是Map中元素的一個“檢視”,而不是“副本” 。問題也就出現在這裡,當一個執行緒正在迭代Map中的元素時,另一個執行緒可能正在修改其中的元素。此時,在迭代元素時就可能會丟擲 ConcurrentModificationException異常。
意識到問題的所在,那解決起來也容易多了,最後我使用了ConcurrentHashMap做了替換。
ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的鎖機制。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個執行緒對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。
ConcurrentHashMap預設將hash表分為16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。這樣,原來只能一個執行緒進入,現在卻能同時有16個寫執行緒執行,併發效能的提升是顯而易見的。
在迭代方面,ConcurrentHashMap使用了一種不同的迭代方式。
在這種迭代方式中,當iterator被建立後集合再發生改變就不再是丟擲ConcurrentModificationException, 取而代之的是在改變時new新的資料從而不影響原有的資料。 iterator完成後再將頭指標替換為新的資料。這樣iterator執行緒可以使用原來老的資料。而寫執行緒也可以併發的完成改變。
至此,問題解決,同時,對於集合的多執行緒又有了一個新的瞭解,所以以後多執行緒還是用ConcurrentHashMap比較好!!!