ConcurrentHashMap深刻分析

##Map體系## Map類圖 Hashtable是JDK 5以前Map惟一線程安全的內置實現(Collections.synchronizedMap不算)。Hashtable繼承的是Dictionary(Hashtable是其惟一公開的子類),並不繼承AbstractMap或者HashMap.儘管Hashtable和HashMap的結構很是相似,可是他們之間並無多大聯繫。 ConcurrentHashMap是HashMap的線程安全版本,ConcurrentSkipListMap是TreeMap的線程安全版本。 最終可用的線程安全版本Map實現是ConcurrentHashMap、ConcurrentSkipListMap、Hashtable、Properties四個,可是Hashtable是過期的類庫,所以若是能夠的應該儘量的使用ConcurrentHashMap和ConcurrentSkipListMap.html

##簡介## ConcurrentHashMap 是 java.util.concurrent 包的重要成員。本文將結合 Java 內存模型,分析 JDK 源代碼,探索 ConcurrentHashMap 高併發的具體實現機制。 因爲 ConcurrentHashMap 的源代碼實現依賴於 Java 內存模型,因此閱讀本文須要讀者瞭解 Java 內存模型。同時,ConcurrentHashMap 的源代碼會涉及到散列算法和鏈表數據結構,因此,讀者須要對散列算法和基於鏈表的數據結構有所瞭解。java

###Java 內存模型### Java 語言的內存模型由一些規則組成,這些規則肯定線程對內存的訪問如何排序以及什麼時候能夠確保它們對線程是可見的。下面咱們將分別介紹 Java 內存模型的重排序,內存可見性和 happens-before 關係。 ####重排序#### 內存模型描述了程序的可能行爲。具體的編譯器實現能夠產生任意它喜歡的代碼 -- 只要全部執行這些代碼產生的結果,可以和內存模型預測的結果保持一致。這爲編譯器實現者提供了很大的自由,包括操做的重排序。 編譯器生成指令的次序,能夠不一樣於源代碼所暗示的「顯然」版本。重排序後的指令,對於優化執行以及成熟的全局寄存器分配算法的使用,都是大有脾益的,它使得程序在計算性能上有了很大的提高。 重排序類型包括:算法

  • 編譯器生成指令的次序,能夠不一樣於源代碼所暗示的「顯然」版本。
  • 處理器能夠亂序或者並行的執行指令。
  • 緩存會改變寫入提交到主內存的變量的次序。 ####內存可見性#### 因爲現代可共享內存的多處理器架構可能致使一個線程沒法立刻(甚至永遠)看到另外一個線程操做產生的結果。因此 Java 內存模型規定了 JVM 的一種最小保證:何時寫入一個變量對其餘線程可見。 在現代可共享內存的多處理器體系結構中每一個處理器都有本身的緩存,並週期性的與主內存協調一致。假設線程 A 寫入一個變量值 V,隨後另外一個線程 B 讀取變量 V 的值,在下列狀況下,線程 B 讀取的值可能不是線程 A 寫入的最新值:
  • 執行線程 A 的處理器把變量 V 緩存到寄存器中。
  • 執行線程 A 的處理器把變量 V 緩存到本身的緩存中,但尚未同步刷新到主內存中去。
  • 執行線程 B 的處理器的緩存中有變量 V 的舊值。 ####Happens-before 關係#### happens-before 關係保證:若是線程 A 與線程 B 知足 happens-before 關係,則線程 A 執行動做的結果對於線程 B 是可見的。若是兩個操做未按 happens-before 排序,JVM 將能夠對他們任意重排序。 下面介紹幾個與理解 ConcurrentHashMap 有關的 happens-before 關係法則:
  • 程序次序法則:若是在程序中,全部動做 A 出如今動做 B 以前,則線程中的每動做 A 都 happens-before 於該線程中的每個動做 B。
  • 監視器鎖法則:對一個監視器的解鎖 happens-before 於每一個後續對同一監視器的加鎖。
  • Volatile 變量法則:對 Volatile 域的寫入操做 happens-before 於每一個後續對同一 Volatile 的讀操做。
  • 傳遞性:若是 A happens-before 於 B,且 B happens-before C,則 A happens-before C。 ##ConcurrentHashMap結構分析## 經過ConcurrentHashMap的類圖來分析ConcurrentHashMap的結構。 ConcurrentHashMap類圖 ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap相似,是一種數組和鏈表結構, 一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。 ConcurrentHashMap結構 ConcurrentHashMap 類中包含兩個靜態內部類 HashEntry 和 Segment.HashEntry 用來封裝映射表的鍵 / 值對;Segment 用來充當鎖的角色,每一個 Segment 對象守護整個散列映射表的若干個桶。每一個桶是由若干個 HashEntry 對象連接起來的鏈表。一個 ConcurrentHashMap 實例中包含由若干個 Segment 對象組成的數組。

##ConcurrentHashMap原理實現## ###鎖分離 (Lock Stripping)### 好比HashTable是一個過期的容器類,經過使用synchronized來保證線程安全,在線程競爭激烈的狀況下HashTable的效率很是低下。緣由是全部訪問HashTable的線程都必須競爭同一把鎖。那假如容器裏有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裏不一樣數據段的數據時,線程間就不會存在鎖競爭,從而能夠有效的提升併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術。 ConcurrentHashMap內部使用段(Segment)來表示這些不一樣的部分,每一個段其實就是一個小的hash table,它們有本身的鎖。只要多個修改操做發生在不一樣的段上,它們就能夠併發進行。一樣當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。 ConcurrentHashMap有些方法須要跨段,好比size()和containsValue(),它們可能須要鎖定整個表而而不只僅是某個段,這須要按順序鎖定全部段,操做完畢後,又按順序釋放全部段的鎖。這裏"按順序"是很重要的,不然極有可能出現死鎖,在ConcurrentHashMap內部,段數組是final的,而且其成員變量實際上也是final的,可是,僅僅是將數組聲明爲final的並不保證數組成員也是final的,這須要實現上的保證。這能夠確保不會出現死鎖,由於得到鎖的順序是固定的。不變性是多線程編程佔有很重要的地位,下面還要談到。 final Segment<K,V>[] segments; ###不變(Immutable)和易變(Volatile)### ConcurrentHashMap徹底容許多個讀操做併發進行,讀操做並不須要加鎖。若是使用傳統的技術,如HashMap中的實現,若是容許能夠在hash鏈的中間添加或刪除元素,讀操做不加鎖將獲得不一致的數據。ConcurrentHashMap實現技術是保證HashEntry幾乎是不可變的。HashEntry表明每一個hash鏈中的一個節點,其結構以下所示:編程

static final class HashEntry<K,V> { 
     final K key;                       // 聲明 key 爲 final 型
     final int hash;                   // 聲明 hash 值爲 final 型 
     volatile V value;                 // 聲明 value 爲 volatile 型
     final HashEntry<K,V> next;      // 聲明 next 爲 final 型 

      HashEntry(K key, int hash, HashEntry<K,V> next, V value) { 
         this.key = key; 
         this.hash = hash; 
         this.next = next; 
         this.value = value; 
     }
  }

能夠看到除了value不是final的,其它值都是final的,這意味着不能從hash鏈的中間或尾部添加或刪除節點,由於這須要修改next引用值,全部的節點的修改只能從頭部開始。對於put操做,能夠一概添加到Hash鏈的頭部。可是對於remove操做,可能須要從中間刪除一個節點,這就須要將要刪除節點的前面全部節點整個複製一遍,最後一個節點指向要刪除結點的下一個結點。這在講解刪除操做時還會詳述。爲了確保讀操做可以看到最新的值,將value設置成volatile,這避免了加鎖。數組

##ConcurrentHashMap具體實現## ###ConcurrentHashMap初始化### ConcurrentHashMap初始化方法是經過initialCapacity,loadFactor, concurrencyLevel幾個參數來初始化segments數組,段偏移量segmentShift,段掩碼segmentMask和每一個segment裏的HashEntry數組,初始化segments數組。讓咱們來看一下初始化segmentShift,segmentMask和segments數組的源代碼。緩存

http://java.chinaitlab.com/line/914247.html http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap http://www.blogjava.net/DLevin/archive/2013/10/18/405030.html安全

相關文章
相關標籤/搜索