1. 程式人生 > >HashMap與ConcurrentHashMap的區別與應用場景

HashMap與ConcurrentHashMap的區別與應用場景

從JDK1.5開始加入了ConcurrentHashMap,在面試的過程中我問過很多程式設計師,HashpMap與ConcurrentHashMap的區別是什麼呢。我得到的回答一般都是HashpMap不是執行緒安全的,ConcurrentHashMap是執行緒安全的。這個結論很籠統。下面我來詳細的舉例說明他們之間的區別。

1 HashMap與ConcurrentHashMap多執行緒同步的誤區

使用了ConcurrentHashMap就意味著多執行緒環境中的資料是執行緒同步的嗎?

答案是不一定。用以下程式碼來作為示例來說明.

1.1 編碼思路

  • 分別建立一個全域性HashMap和一個全域性ConcurrentHashMap
  • 分別賦予初始值。
  • 多執行緒修改這兩個集合
  • 然後輸出值,比較最終結果。

1.2 需要驗證的結論

  • HashMap在多執行緒情況下操作不能保證資料同步。
  • ConcurrentHashMap多執行緒操作同樣不能保證資料同步。

1.3 驗證程式碼

  • Demo1類,驗證HashMap多執行緒操作資料不同步.
package com.dashidan.faq;import java.util.HashMap;import java.util.Map;/**
 *
 * 大屎蛋教程網-dashidan.com
 * HashMap與ConcurrentHashMap的區別於應用場景
 * Created by 大屎蛋 on 2018/5/18.
 */
publicclassDemo1{publicstaticvoid main(String[] args){/** 全域性HashMap*/HashMap<Integer,Integer> hashMap =newHashMap(); hashMap.put(0,0);/** 多執行緒編輯100次*/for(int i =0; i <100; i++){newThread(newEditThread(hashMap)).start();}/** 讀取執行緒*/newThread(newReadThread(hashMap)).start();/** 輸出最終結果*/System.out.println
("Demo1 main value "+ hashMap.get(0));}}classEditThreadimplementsRunnable{Map<Integer,Integer> hashMap;publicEditThread(Map<Integer,Integer> hashMap){this.hashMap = hashMap;}@Overridepublicvoid run(){ hashMap.put(0, hashMap.get(0)+1);}}classReadThreadimplementsRunnable{Map<Integer,Integer> hashMap;publicReadThread(Map<Integer,Integer> hashMap){this.hashMap = hashMap;}@Overridepublicvoid run(){System.out.println("value "+ hashMap.get(0));}}

輸出結果

Demo1 main value76value94

這裡每次執行的結果可能會不一樣。

  • Demo2類,驗證ConcurrentHashMap多執行緒操作資料不同步.
package com.dashidan.faq;import java.util.concurrent.ConcurrentHashMap;/**
 * Created by bj on 2018/5/18.
 */publicclassDemo2{publicstaticvoid main(String[] args){/** 全域性ConcurrentHashMap*/ConcurrentHashMap<Integer,Integer> hashMap =newConcurrentHashMap();
        hashMap.put(0,0);/** 多執行緒編輯100次*/for(int i =0; i <100; i++){newThread(newEditThread(hashMap)).start();}/** 讀取執行緒*/newThread(newReadThread(hashMap)).start();/** 輸出最終結果*/System.out.println("Demo2 main value "+ hashMap.get(0));}}

輸出結果:

Demo2 main value81value84

多次執行,輸出的結果可能不一致。這樣說明多執行緒修改ConcurrentHashMap中的資料,不能保證多執行緒同步。需要進行加鎖或者其他能達到執行緒同步的方式配合使用。

2 HashMap應用場景

2.1 HashpMap多執行緒情況下的ConcurrentModificationException

當方法檢測到物件的併發修改(單執行緒情況也可能),但不允許這種修改時,丟擲ConcurrentModificationException異常。

package com.dashidan.faq;import java.util.HashMap;import java.util.Map;import java.util.Random;/**
 * 大屎蛋教程網-dashidan.com
 * HashMap與ConcurrentHashMap的區別於應用場景
 * Created by 大屎蛋 on 2018/5/18.
 */publicclassDemo3{publicstaticvoid main(String[] args){/** 全域性HashMap*/HashMap<Integer,Integer> hashMap =newHashMap();/** 多執行緒編輯100次*/for(int i =0; i <1000; i++){
            hashMap.put(i, i);}newThread(newAddThread(hashMap)).start();newThread(newRemoveThread(hashMap)).start();/** 讀取執行緒*/newThread(newIteratorThread(hashMap)).start();/** 輸出最終結果*/}}classAddThreadimplementsRunnable{Map<Integer,Integer> hashMap;publicAddThread(Map<Integer,Integer> hashMap){this.hashMap = hashMap;}@Overridepublicvoid run(){while(true){int random =newRandom().nextInt();
            hashMap.put(random, random);}}}classRemoveThreadimplementsRunnable{Map<Integer,Integer> hashMap;publicRemoveThread(Map<Integer,Integer> hashMap){this.hashMap = hashMap;}@Overridepublicvoid run(){int random =newRandom().nextInt(1000);while(true){
            hashMap.remove(random);}}}classIteratorThreadimplementsRunnable{Map<Integer,Integer> hashMap;publicIteratorThread(Map<Integer,Integer> hashMap){this.hashMap = hashMap;}@Overridepublicvoid run(){System.out.println("------------------ "+ hashMap.size());for(Integervalue: hashMap.values()){//            System.out.println("value " + value);}System.out.println("+++++++++++++++++++ "+ hashMap.size());}}

輸出結果:

------------------1259Exceptionin thread "Thread-2" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$ValueIterator.next(HashMap.java:1458)
    at com.dashidan.faq.IteratorThread.run(Demo3.java:78)
    at java.lang.Thread.run(Thread.java:745

輸出結果可能不一致,多次執行有機率出現ConcurrentModificationException異常.這個異常會導致程式執行停止.

2.3 推薦HashMap應用場景

上文中的輸出示例驗證了多執行緒操作下HashMap無法保證資料同步,多執行緒修改HashMap並且有遍歷的操作時,可能會產生ConcurrentModificationException異常。所以,推薦的HashMap應用場景是單執行緒執行環境,並且不需要遍歷操作的場景。

這個推薦場景不是硬性條件。比如多線操作HashMap,我們通過加鎖或者加入同步控制依然能正常應用HashMap,只是需要加上同步操作的代價。

3 推薦ConcurrentHashMap應用場景

ConcurrentHashMap所有操作都是執行緒安全的,但獲取操作不必鎖定,並且不支援以某種防止所有訪問的方式鎖定整個表。獲取操作(包括 get)通常不會受阻塞,因此,可能與更新操作交迭(包括 put 和 remove)。在建立迭代器/列舉時或自此之後,Iterators 和 Enumerations返回在某一時間點上影響雜湊表狀態的元素。它們不會 丟擲 ConcurrentModificationException。

在上文中的HashMap示例中,我們將HashMap改為ConcurrentHashMap,來看看會發生什麼.以下示例程式碼:

package com.dashidan.faq;import java.util.concurrent.ConcurrentHashMap;/**
 * 大屎蛋教程網-dashidan.com
 * HashMap與ConcurrentHashMap的區別於應用場景
 * Created by 大屎蛋 on 2018/5/18.
 */publicclassDemo4{publicstaticvoid main(String[] args){/** 全域性ConcurrentHashMap*/ConcurrentHashMap<Integer,Integer> hashMap =newConcurrentHashMap();/** 多執行緒編輯1000次*/for(int i =0; i <1000; i++){
            hashMap.put(i, i);}newThread(newAddThread(hashMap)).start();newThread(newRemoveThread(hashMap)).start();/** 讀取執行緒*/newThread(newIteratorThread(hashMap)).start();}}

輸出結果

------------------1381+++++++++++++++++++3072

每次執行輸出結果可能會不一致。這個是多執行緒操作下,不能保證插入順序,所以插入的隨機值位置不固定。輸出遍歷前h後的ConcurrentHashMap長度不一致。

這種情況說明,在遍歷ConcurrentHashMap時如果遍歷過程中,該集合的機構發生變化,比如put,remove資料。這時不會丟擲ConcurrentModificationException,能夠正常遍歷完成ConcurrentHashMap.

  • ConcurrentHashMap推薦應用場景 多執行緒對HashMap資料新增刪除操作時,可以採用ConcurrentHashMap。