1. 程式人生 > >非執行緒安全的HashMap 和 執行緒安全的ConcurrentHashMap

非執行緒安全的HashMap 和 執行緒安全的ConcurrentHashMap

在平時開發中,我們經常採用HashMap來作為本地快取的一種實現方式,將一些如系統變數等資料量比較少的引數儲存在HashMap中,並將其作 為單例類的一個屬性。在系統執行中,使用到這些快取資料,都可以直接從該單例中獲取該屬性集合。但是,最近發現,HashMap並不是執行緒安全的,如果你 的單例類沒有做程式碼同步或物件鎖的控制,就可能出現異常。

首先看下在多執行緒的訪問下,非現場安全的HashMap的表現如何,在網上看了一些資料,自己也做了一下測試:

 1publicclass MainClass {
 2    
 3    publicstaticfinal HashMap<String, String
> firstHashMap=new HashMap<String, String>();
 4    
 5    publicstaticvoid main(String[] args) throws InterruptedException {
 6        
 7        //執行緒一 8        Thread t1=new Thread(){
 9            publicvoid run() {
10                for(int i=0;i<25;i++){
11                    firstHashMap.put(String.valueOf(i), String.valueOf(i));
12                }
13            }
14        }
;
15        
16        //執行緒二17        Thread t2=new Thread(){
18            publicvoid run() {
19                for(int j=25;j<50;j++){
20                    firstHashMap.put(String.valueOf(j), String.valueOf(j));
21                }
22            }
23        }
;
24        
25        t1.start();
26        t2.start();
27        
28        //主執行緒休眠1秒鐘,以便t1和t2兩個執行緒將firstHashMap填裝完畢。29        Thread.currentThread().sleep(1000);
30        
31        for(int l=0;l<50;l++){
32            //如果key和value不同,說明在兩個執行緒put的過程中出現異常。33if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){
34                System.err.println(String.valueOf(l)+":"+firstHashMap.get(String.valueOf(l)));
35            }
36        }
37        
38    }
39
40}

上面的程式碼在多次執行後,發現表現很不穩定,有時沒有異常文案打出,有時則有個異常出現:


為什麼會出現這種情況,主要看下HashMap的實現:
 1public V put(K key, V value) {
 2    if (key ==null)
 3        return putForNullKey(value);
 4        int hash = hash(key.hashCode());
 5        int i = indexFor(hash, table.length);
 6        for (Entry<K,V> e = table[i]; e !=null; e = e.next) {
 7            Object k;
 8            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
 9                V oldValue = e.value;
10                e.value = value;
11                e.recordAccess(this);
12                return oldValue;
13            }
14        }
15
16        modCount++;
17        addEntry(hash, key, value, i);
18        returnnull;
19    }

我覺得問題主要出現在方法addEntry,繼續看:
1void addEntry(int hash, K key, V value, int bucketIndex) {
2    Entry<K,V> e = table[bucketIndex];
3        table[bucketIndex] =new Entry<K,V>(hash, key, value, e);
4        if (size++>= threshold)
5            resize(2* table.length);
6    }

從程式碼中,可以看到,如果發現雜湊表的大小超過閥值threshold,就會呼叫resize方法,擴大容量為原來的兩倍,而擴大容量的做法是新建一個 Entry[]:
 1void resize(int newCapacity) {
 2        Entry[] oldTable = table;
 3        int oldCapacity = oldTable.length;
 4        if (oldCapacity == MAXIMUM_CAPACITY) {
 5            threshold = Integer.MAX_VALUE;
 6            return;
 7        }
 8
 9        Entry[] newTable =new Entry[newCapacity];
10        transfer(newTable);
11        table = newTable;
12        threshold = (int)(newCapacity * loadFactor);
13    }

一般我們宣告HashMap時,使用的都是預設的構造方法:HashMap<K,V>,看了程式碼你會發現,它還有其它的構造方法:HashMap(int initialCapacity, float loadFactor),其中引數initialCapacity為初始容 量,loadFactor為載入因子,而之前我們看到的threshold = (int)(capacity * loadFactor); 如果在預設情況下,一個HashMap的容量為16,載入因子為0.75,那麼閥值就是12,所以在往HashMap中put的值到達12時,它將自動擴 容兩倍,如果兩個執行緒同時遇到HashMap的大小達到12的倍數時,就很有可能會出現在將oldTable轉移到newTable的過程中遇到問題,從 而導致最終的HashMap的值儲存異常。

JDK1.0引入了第一個關聯的集合類HashTable,它是執行緒安全的。 HashTable的所有方法都是同步的。
JDK2.0引入了HashMap,它提供了一個不同步的基類和一個同步的包裝器synchronizedMap。synchronizedMap被稱為 有條件的執行緒安全類。
JDK5.0util.concurrent包中引入對Map執行緒安全的實現ConcurrentHashMap,比起synchronizedMap, 它提供了更高的靈活性。同時進行的讀和寫操作都可以併發地執行。

所以在開始的測試中,如果我們採用ConcurrentHashMap,它的表現就很穩定,所以以後如果使用Map實現本地快取,為了提高併發時的穩定 性,還是建議使用ConcurrentHashMap。


====================================================================

另外,還有一個我們經常使用的ArrayList也是非執行緒安全的,網上看到的有一個解釋是這樣:
一個 ArrayList 類,在新增一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在單執行緒執行的情況下,如果 Size = 0,新增一個元素後,此元素在位置 0,而且 Size=1;
而如果是在多執行緒情況下,比如有兩個執行緒,執行緒 A 先將元素存放在位置 0。但是此時 CPU 排程執行緒A暫停,執行緒 B 得到執行的機會。執行緒B也將元素放在位置0,(因為size還未增長),完了之後,兩個執行緒都是size++,結果size變成2,而只有 items[0]有元素。
util.concurrent包也提供了一個執行緒安全的ArrayList替代者CopyOnWriteArrayList。

本文轉載自:http://www.blogjava.net/lukangping/articles/331089.html