日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ConcurrentHashMap深入分析

發布時間:2024/4/14 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ConcurrentHashMap深入分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

##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.

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

###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是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數據。一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素, 每個Segment守護者一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。 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

轉載于:https://my.oschina.net/xianggao/blog/212060

總結

以上是生活随笔為你收集整理的ConcurrentHashMap深入分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。