Java多線程ConcurrentHashMap深度解讀

前言:ConcurrentHashMap是非常經典的一個類,面試中會被經常問到,因爲它裏面用了非常複雜的數據結構,設計上也非常精緻,同時又涉及併發編程,可以說是個寶藏類,我會嘗試解讀一下這個類。

(我會抽空一直更新)

它的代碼高達6300行

一 註釋

我們來看一下類的註釋:

上面貼出來的是第一段註釋,後面我就不貼圖了,直接翻譯:

第一段:一個支持完全併發讀和高期望併發更新的hash表,這個類和HashTable實現了同樣的功能,包含了HashTable中所有的方法。然而,儘管(這個類)所有的操作都是線程安全的,取數據操作沒有使用鎖,而且不支持鎖住整個表(即阻止所有對錶的操作)。在依賴這個類的線程安全同時又不依賴它的實現細節的程序中,它和HashTable是可以互操作的。

第二段:取數據操作(包括get())通常不會阻塞,所以可能會和更新操作(包括put()和remove())重疊。取數據反映最近結束的更新操作的結果。(對一個給定的key的更新操作忍受了一個在更新之前發生的關係——這個關係是和任意的取這個數據的操作,(這一段不知道在說什麼,建議大家自己看))。對於批量化的操作例如putAll和clear,併發的取操作可能會反映出僅僅一小部分的entry(基本單位)的插入或者刪除。相似地,Iterators, Spliterators 和 Enumerations返回在一定程度上反映hash表在或者自從iterator/enumeration創建時的狀態的元素,他們不拋出ConcurrentModificationException異常。然而,iterators(迭代器)被設計爲一次只能供一個線程去使用。記住,用來彙總狀態的方法(包括size,isEmpty,containsValue)返回的結果,只在該map不在其它的線程中進行併發更新的時候纔有用,否則,這些方法返回的結果反映了短暫的狀態,這些狀態足夠用於監控或者評估目的,但是不夠用於程序控制。

如果有太多的碰撞(例如,擁有不同hash值的key,映射到了同一個slot),表會動態擴張,以實現一個期望的均值上的效果——維持大約一個mapping中有兩個bin(等價於裝載因子爲0.75),在這個均值附近,方差可能會很大,因爲mappings(匹配)被添加和刪除,但是總體上來說,這樣做維持了一個普遍可以接受的時間和空間上的均衡。然而,改變這個或者其它hash表的規模可能時一個相對較慢的操作。如果可能的話,提供一個hash表大小的評估,作爲這個類的構造函數的可續參數initialCapacity的初值,是一個非常好的做法。另一個可選的參數loadFactor(裝載因子)提供了進一步定製初始table的大小的方法——通過具體化table的密度,密度被用來計算空間的大小,這些空間被分配給定的個數的元素。爲了和這個類的以前的版本兼容,它的構造函數可能會有選擇的指定concurrencyLevel參數,作爲額外的反映table內部大小的線索。注意,使用很多具有相同hash值的key一定會降低任意hash表的表現。爲了改進影響,當key是可比較的,這個類可能會在key之間使用比較的順序來打破關係。

2019.10.14更新

ConcurrentHashMap的Set映射可能是使用newKeySet()或者newKeySet(int)創建的,或者使用keySet(Object)觀測當keys是我們感興趣的,而且映射的values是沒有(或許暫時的)使用的或者都映射了相同的value。

ConcurrentHashMap可以被用來作爲尺寸可變的頻繁map(一種直方圖或者多集的模式),通過使用LongAdder類的值並且使用couputeIfAbsent進行初始化。例如,爲了向ConcurrentHashMap<String,LongAdder> freqs添加一個值(就是說把它加1),你可以使用:

freqs.computeIfAbsent(k -> new LongAdder()).increment();

這個類和它的views以及iterators實現了Map和Iterator接口的所有的可選的方法。

像Hashtable但是不像HashMap,這個類不允許null值作爲key或者value.

ConcurrentHashMap支持一系列連續而且平行的桶操作,不像大部分的Stream方法,這些桶操作被設計爲安全而且通常是能夠感受到的,甚至被用於被其它線程同步更新的maps;例如,當計算一個共享的註冊表中的值的快照和。有三種操作,每一種操作有四種形式,接受使用Keys,Values,Entries和(Key,Value)作爲參數和/或返回值的函數。因爲ConcurrentHashMap的元素沒有按照特定的方法進行排序,而且可能在不同的平行的執行中被按照不同的順序處理。函數的正確性不應該依賴任何順序,或者任何其它的當計算在發生時可能短暫改變對象或者值。而且除了forEach操作,其它的都應該在理想的情況下是沒有副作用的。在Entry上的桶操作不支持方法setValue。

forEach:對每一個元素執行操作。在執行這個操作之前,一個可變的形式在每一個元素上應用一個給定的轉化。

search:返回第一個非null的在每一個元素上應用了給定的函數的結果。當一個元素被發現,就會跳過進一步的搜索。

reduce:收集每一個元素,支持的減少函數不能夠依賴順序(更官方的說法是,它應該是可關聯而且可交換的。)有5個變形:

Plain reductions。(沒有格式爲(Key,Value)作爲函數參數的這個方法,因爲沒有相對應的返回值。)

Mapped reductions,收集作用於每一個元素的函數的結果。

Reductions使用基礎值作用於標量doubles,longs,ints。

這些桶操作接受parallelismThreshold參數,如果當前的map的size被檢測爲小於給定的閾值,方法會連續執行。使用Long.MAX_VALUE作爲並行度的上限,使用值1會造成最大的並行度,最大並行度通過將主任務分離爲足夠的子任務來充分利用commonPool()來實現。通常,你在開始的時候可能會選擇這些極端值的一個,然後選擇能夠平衡開銷和吞吐量的中間值。

這些桶操作中的併發屬性跟隨自ConcurrentHashMap中的併發屬性:任意非空的get(key)的返回值、相關的取數據的方法,忍受了一個提前發生的和關聯的插入或者更新的關係。任意桶操作的結果反映了每一對元素間關係的結合(但是不是必須是原子的,當考慮到map是一個整體, 除非它被認爲是靜止的)。相反地,因爲map中的keys和values都不是null,null可以作爲一個非常可靠的結果缺失的指示器。爲了維持這個屬性,null作爲一個隱式的所有非標量減少操作的基礎。對於double、long和int版本,這個基礎應該是:當它和任何值結合的時候,返回那個值(它應該是減少的標識元素)。大多數的減少操作擁有

2019.10.17更新

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章