1. 程式人生 > >一個異常引發的對Hashtable和HashMap的思考

一個異常引發的對Hashtable和HashMap的思考

  對於Hashtable和HashMap,相信每個學習Java的人都不會陌生,這兩個集合在用法上並沒有什麼不同,但在使用環境上卻有很大差別:

1)區別,這兩個類主要有以下幾方面的不同:      HashtableHashMap都實現了Map介面,但是Hashtable的實現是基於Dictionary抽象類      在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null      當get()方法返回null值時,即可以表示 HashMap中沒有該鍵,也可以表示該鍵所對應的值為null 因此,在HashMap中不能由get()方法來判斷HashMap

中是否存在某個鍵,而應該用containsKey()方法來判斷。而在Hashtable中,無論是key還是value都不能為null 這兩個類最大的不同在於:1Hashtable是執行緒安全的,它的方法是同步了的,可以直接用在多執行緒環境中。2)而HashMap則不是執行緒安全的。在多執行緒環境中,需要手動實現同步機制。

     上面所提的是面試官非常熱衷提問的(當然有水平的公司也會問的更深,比如HashMap的實現機制),但這並非今天要說的重點。

     本人學習Java語言多年,自認對這兩個類瞭如指掌,可是一次變成過程中所遇到的異常,讓我發現自己對這兩個類的理解還不夠深入。

     前段時間,本人使用Java語言實現了一個時間排程演算法,這個演算法是本人一片論文中的,該演算法涉及多執行緒,所以我理所應當的用到了HashTable,但在測試階段卻頻繁的跳出一個異常,ConcurrentModificationException,雖然很快得到了很好的解決,但這個異常,使我對HashMap,Hashtable,ConcurrentHashMap有了一個更深層的認識。

     首先,很多人都知道,多執行緒情況下應該使用HashTable,或是使用CollectionsSynchronizedMap對HashMap進行同步。但看似同步的Map卻仍然存在潛在的執行緒安全問題,可以考慮下面一種場景

如下面這段程式碼:Java程式碼

<pre class="java" name="code"><span style="font-size:18px;">// shm是SynchronizedMap的一個例項   
    if(shm.containsKey('key')){   
        shm.remove(key);   
       }  
</span>


這段程式碼用於從map中刪除一個元素之前判斷是否存在這個元素。這裡的containsKeyreomve方法都是同步的,但是整段程式碼卻不是。為什麼?假設:執行緒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比較好!!!