集合框架—常用的map集合


1、Collections.synchronizedMap()

 

實現上在調用map所有方法時,都對整個map進行同步,而ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。所以,只要要有一個線程訪問map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap某個桶時,其他線程,仍然可以對map執行某些操作。這樣,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優勢。同時,同步操作精確控制到桶,所以,即使在遍歷map時,其他線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException

ConcurrentHashMap從類的命名就能看出,它必然是個HashMapCollections.synchronizedMap()可以接收任意Map實例作為封裝。

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

下面的例子顯示java.util.Collections.synchronizedMap()方法的使用

package com.yiibai; 

import java.util.*; 

public class CollectionsDemo {

   public static void main(String[] args) {

      // create map

      Map<String,String> map = new HashMap<String,String>();     

      // populate the map

      map.put("1","TP");

      map.put("2","IS");

      map.put("3","BEST");     

      // create a synchronized map

      Map<String,String> synmap = Collections.synchronizedMap(map);    

      System.out.println("Synchronized map is :"+synmap);

   }

}

現在編譯和運行上面的代碼示例,將產生以下結果。

Synchronized map is :{3=BEST, 2=IS, 1=TP} 

也不會拋出ConcurrentModificationException

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

 

2、ConcurrentHashMap

 

1:線程安全,

2:Iterator的迭代方式采用的是fail-safe的錯誤機制

ConcurrentHashMap在線程安全的基礎上提供了更好的寫並發能力,但同時降低了對讀一致性的要求,ConcurrentHashMap的設計與實現非常精巧,大量的利用了volatile,final,CAS等lock-free技術來減少鎖競爭對於性能的影響.

3:ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。

4:同時,同步操作精確控制到桶,所以,即使在遍歷map時,其他線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException

 

ConcurrentHashMap采用了分段鎖的設計,只有在同一個分段內才存在競態關系,不同的分段鎖之間沒有鎖競爭。相比於對整個Map加鎖的設計,分段鎖大大的提高了高並發環境下的處理能力。但同時,由於不是對整個Map加鎖,導致一些需要掃描整個Map的方法(如size(), containsValue())需要使用特殊的實現,另外一些方法(如clear())甚至放棄了對一致性的要求(ConcurrentHashMap是弱一致性的,

ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

源代碼:

static final class Segment<K,V> extends ReentrantLock implements Serializable{ 

並發度

並發度可以理解為程序運行時能夠同時更新ConccurentHashMap且不產生鎖競爭的最大線程數,實際上就是ConcurrentHashMap中的分段鎖個數,即Segment[]的數組長度。ConcurrentHashMap默認的並發度為16,但用戶也可以在構造函數中設置並發度。當用戶設置並發度時,ConcurrentHashMap會使用大於等於該值的最小2冪指數作為實際並發度(假如用戶設置並發度為17,實際並發度則為32)。運行時通過將key的高n位(n = 32 – segmentShift)和並發度減1(segmentMask)做位與運算定位到所在的Segment。segmentShift與segmentMask都是在構造過程中根據concurrency level被相應的計算出來。

如果並發度設置的過小,會帶來嚴重的鎖競爭問題;如果並發度設置的過大,原本位於同一個Segment內的訪問會擴散到不同的Segment中,CPU cache命中率會下降,從而引起程序性能下降。(文檔的說法是根據你並發的線程數量決定,太多會導性能降低)

JDK8中的實現

ConcurrentHashMap在JDK8中進行了巨大改動,很需要通過源碼來再次學習下Doug Lea的實現方法。

它摒棄了Segment(鎖段)的概念,而是啟用了一種全新的方式實現,利用CAS算法。它沿用了與它同時期的HashMap版本的思想,底層依然由"數組"+鏈表+紅黑樹的方式思想(JDK7與JDK8中HashMap的實現),但是為了做到並發,又增加了很多輔助的類,例如TreeBin,Traverser等對象內部類。

重要的屬性

首先來看幾個重要的屬性,與HashMap相同的就不再介紹了,這里重點解釋一下sizeCtl這個屬性。可以說它是ConcurrentHashMap中出鏡率很高的一個屬性,因為它是一個控制標識符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含義。

  • 負數代表正在進行初始化或擴容操作
  • -1代表正在初始化
  • -N 表示有N-1個線程正在進行擴容操作
  • 正數或0代表hash表還沒有被初始化,這個數值表示初始化或下一次進行擴容的大小,這一點類似於擴容閾值的概念。還后面可以看到,它的值始終是當前ConcurrentHashMap容量的0.75倍,這與loadfactor是對應的。

總結

JDK6,7中的ConcurrentHashmap主要使用Segment來實現減小鎖粒度,把HashMap分割成若干個Segment,在put的時候需要鎖住Segment,get時候不加鎖,使用volatile來保證可見性,當要統計全局時(比如size),首先會嘗試多次計算modcount來確定,這幾次嘗試中,是否有其他線程進行了修改操作,如果沒有,則直接返回size。如果有,則需要依次鎖住所有的Segment來計算。

jdk7中ConcurrentHashmap中,當長度過長碰撞會很頻繁,鏈表的增改刪查操作都會消耗很長的時間,影響性能,所以jdk8 中完全重寫了concurrentHashmap,代碼量從原來的1000多行變成了 6000多 行,實現上也和原來的分段式存儲有很大的區別。

主要設計上的變化有以下幾點:

  1. 不采用segment而采用node,鎖住node來實現減小鎖粒度。
  2. 設計了MOVED狀態 當resize的中過程中 線程2還在put數據,線程2會幫助resize。
  3. 使用3個CAS操作來確保node的一些操作的原子性,這種方式代替了鎖。
  4. sizeCtl的不同值來代表不同含義,起到了控制的作用。

至於為什么JDK8中使用synchronized而不是ReentrantLock,我猜是因為JDK8中對synchronized有了足夠的優化吧。

 

3、LinkedHashMap

 

HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比 LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。

 

Map<Integer,String> map = new LinkedHashMap<Integer,String>();

   map.put(6, "apple");

   map.put(3, "banana");

   map.put(2,"pear");

   

   

for (Iterator it =  map.keySet().iterator();it.hasNext();)

   {

    Object key = it.next();

    System.out.println( key+"="+ map.get(key));

   }

運行結果如下:

*************************LinkedHashMap*************

6=apple

3=banana

2=pear

 

 

4、TreeMap

 

1TreeMap 類不僅實現了 Map 接口,還實現了 Map 接口的子接口 java.util.SortedMap 

2TreeMap 類中不允許鍵對象為 null 或是基本數據類型,這是因為 TreeMap 中的對象必須是可排序的(即對象需要實現 java.lang.Comparable 接口) 

3:在創建 TreeMap 對象時,如果使用參數為空的構造方法,則根據 Map 對象的 key 進行排序;如果使用參數為 Comparator 的構造方法,則根據 Comparator 進行排序。 

4:在添加、刪除和定位映射關系上,TreeMap類要比HashMap類的性能差一些,

5:在需要排序時,利用現有的 HashMap,創建一個 TreeMap 類型的實例

 

Java代碼  

  1. import java.util.Collections;  
  2. import java.util.HashMap;  
  3. import java.util.Iterator;  
  4. import java.util.Map;  
  5. import java.util.TreeMap;  
  6.   
  7. public class TestCollection {  
  8.   
  9.     public static void main(String[] args) {  
  10.         System.out.println("開始:");    
  11.                 
  12.         Person person1 = new Person("馬先生"220181);  
  13.         Person person2 = new Person("李先生"220193);  
  14.         Person person3 = new Person("王小姐"220186);  
  15.           
  16.         Map<Number, Person> map = new HashMap<Number, Person>();  
  17.         map.put(person1.getId_card(), person1);  
  18.         map.put(person2.getId_card(), person2);  
  19.         map.put(person3.getId_card(), person3);  
  20.           
  21.         // HashMap  
  22.         System.out.println("HashMap,無序:");  
  23.         for (Iterator<Number> it = map.keySet().iterator(); it.hasNext();) {  
  24.             Person person = map.get(it.next());  
  25.             System.out.println(person.getId_card() + " " + person.getName());  
  26.         }  
  27.           
  28.         // TreeMap  
  29.         System.out.println("TreeMap,升序:");  
  30. //創建 TreeMap 時,如果使用參數為空的構造方法,則根據 Map 對象的 key 進行排序
  31.         TreeMap<Number, Person> treeMap = new TreeMap<Number, Person>();  
  32.         //將指定映射中的所有映射關系復制到此映射中。
  33. treeMap.putAll(map);           for (Iterator<Number> it = treeMap.keySet().iterator(); it.hasNext();) {  
  34.             Person person = treeMap.get(it.next());  
  35.             System.out.println(person.getId_card() + " " + person.getName());  
  36.         }  
  37.           
  38.         System.out.println("TreeMap,降序:");
  39. //Collections.reverseOrder()返回一個比較器,它強行逆轉指定比較器順序 
  40. //如果使用參數為 Comparator 的構造方法,則根據 Comparator 進行排序。  
  41.         TreeMap<Number, Person> treeMap2 =   
  42.             new TreeMap<Number, Person>(Collections.reverseOrder());  
  43.         treeMap2.putAll(map);  
  44.         for (Iterator it = treeMap2.keySet().iterator(); it.hasNext();) {  
  45.             Person person = (Person) treeMap2.get(it.next());  
  46.             System.out.println(person.getId_card() + " " + person.getName());  
  47.         }  
  48.           
  49.         System.out.println("結束!");  
  50.     }  
  51. }  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM