HashMap的非線程安全性和ConcurrentHasMap

在平時開發中,咱們常常採用HashMap來做爲本地緩存的一種實現方式,將一些如系統變量等數據量比較少的參數保存在HashMap中,並將其做爲單例類的一個屬性。在系統運行中,使用到這些緩存數據,均可以直接從該單例中獲取該屬性集合。可是,最近發現,HashMap並非線程安全的,若是你的單例類沒有作代碼同步或對象鎖的控制,就可能出現異常。

首先看下在多線程的訪問下,非現場安全的HashMap的表現如何,在網上看了一些資料,本身也作了一下測試:
java

 1 public   class  MainClass  {
 2    
 3    public static final HashMap<String, String> firstHashMap=new HashMap<String, String>();
 4    
 5    public static void main(String[] args) throws InterruptedException {
 6        
 7        //線程一
 8        Thread t1=new Thread(){
 9            public void run() {
10                for(int i=;i<25;i++){
11                    firstHashMap.put(String.valueOf(i), String.valueOf(i));
12                }

13            }

14        }
;
15        
16        //線程二
17        Thread t2=new Thread(){
18            public void 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=;l<50;l++){
32            //若是key和value不一樣,說明在兩個線程put的過程當中出現異常。
33            if(!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的實現:
緩存

 1 public  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        return null;
19    }


我以爲問題主要出如今方法addEntry,繼續看:
安全

1 void  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[]:
多線程

 1 void  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。
併發

相關文章
相關標籤/搜索