1、Collections.synchronizedMap()
實現上在調用map所有方法時,都對整個map進行同步,而ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。所以,只要要有一個線程訪問map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap某個桶時,其他線程,仍然可以對map執行某些操作。這樣,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優勢。同時,同步操作精確控制到桶,所以,即使在遍歷map時,其他線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException。
ConcurrentHashMap從類的命名就能看出,它必然是個HashMap。Collections.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多 行,實現上也和原來的分段式存儲有很大的區別。
主要設計上的變化有以下幾點:
- 不采用segment而采用node,鎖住node來實現減小鎖粒度。
- 設計了MOVED狀態 當resize的中過程中 線程2還在put數據,線程2會幫助resize。
- 使用3個CAS操作來確保node的一些操作的原子性,這種方式代替了鎖。
- 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
1:TreeMap 類不僅實現了 Map 接口,還實現了 Map 接口的子接口 java.util.SortedMap。
2:TreeMap 類中不允許鍵對象為 null 或是基本數據類型,這是因為 TreeMap 中的對象必須是可排序的(即對象需要實現 java.lang.Comparable 接口)
3:在創建 TreeMap 對象時,如果使用參數為空的構造方法,則根據 Map 對象的 key 進行排序;如果使用參數為 Comparator 的構造方法,則根據 Comparator 進行排序。
4:在添加、刪除和定位映射關系上,TreeMap類要比HashMap類的性能差一些,
5:在需要排序時,利用現有的 HashMap,創建一個 TreeMap 類型的實例
Java代碼
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.TreeMap;
- public class TestCollection {
- public static void main(String[] args) {
- System.out.println("開始:");
- Person person1 = new Person("馬先生", 220181);
- Person person2 = new Person("李先生", 220193);
- Person person3 = new Person("王小姐", 220186);
- Map<Number, Person> map = new HashMap<Number, Person>();
- map.put(person1.getId_card(), person1);
- map.put(person2.getId_card(), person2);
- map.put(person3.getId_card(), person3);
- // HashMap
- System.out.println("HashMap,無序:");
- for (Iterator<Number> it = map.keySet().iterator(); it.hasNext();) {
- Person person = map.get(it.next());
- System.out.println(person.getId_card() + " " + person.getName());
- }
- // TreeMap
- System.out.println("TreeMap,升序:");
- //創建 TreeMap 時,如果使用參數為空的構造方法,則根據 Map 對象的 key 進行排序
- TreeMap<Number, Person> treeMap = new TreeMap<Number, Person>();
- //將指定映射中的所有映射關系復制到此映射中。
- treeMap.putAll(map); for (Iterator<Number> it = treeMap.keySet().iterator(); it.hasNext();) {
- Person person = treeMap.get(it.next());
- System.out.println(person.getId_card() + " " + person.getName());
- }
- System.out.println("TreeMap,降序:");
- //Collections.reverseOrder()返回一個比較器,它強行逆轉指定比較器順序
- //如果使用參數為 Comparator 的構造方法,則根據 Comparator 進行排序。
- TreeMap<Number, Person> treeMap2 =
- new TreeMap<Number, Person>(Collections.reverseOrder());
- treeMap2.putAll(map);
- for (Iterator it = treeMap2.keySet().iterator(); it.hasNext();) {
- Person person = (Person) treeMap2.get(it.next());
- System.out.println(person.getId_card() + " " + person.getName());
- }
- System.out.println("結束!");
- }
- }