摘要:與和是一一對應的,對充當鎖的角色,每當對數組的數據進行修改時,首先要獲取對應的鎖解決散列沖突的方式是采用分離鏈表法分散鏈表法使用鏈表解決沖突,將散列值相同的元素都保存到一個鏈表中。負載因子,默認為。
一、為什么要用ConcurrentHashMap?
1、HashMap線程不安全,并且進行put操作會導致死循環(由于HashMap的Entry鏈表形成環形數據結構,Entry下的next節點永遠不為空)
2、HashTable多線程效率低下,主要表現在數據操作方法頭采用synchronized互斥鎖,使得其他線程訪問同步方法會進入阻塞或者是輪詢狀態(就是線程會無限循環查看鎖是否被釋放)
3、ConcurrentHashMap的鎖分段技術可提升并發訪問效率(表現在吞吐量)
解析:Segment是一種可重入鎖,在ConcurrentHashMap中扮演鎖的角色,HashEntry則是用于存儲鍵值對數據。ConcurrentHashMap與Segment和HashEntry是一一對應的,Segment對HashEntry充當鎖的角色,每當對HashEntry數組的數據進行修改時,首先要獲取對應的Segment鎖
解決散列沖突的方式是采用分離鏈表法:
分散鏈表法使用鏈表解決沖突,將散列值相同的元素都保存到一個鏈表中。當查詢的時候,首先找到元素所在的鏈表,然后遍歷鏈表查找對應的元素,也就是會根據key定位到鏈表的某個位置,所以不管是hashMap還是concurrentHashMap相同key是只取最后插入的值
圖片來自 http://faculty.cs.niu.edu/~fr...
capacity:當前數組容量,始終保持 2^n,可以擴容,擴容后數組大小為當前的 2 倍,默認為16,即默認允許16個線程同時訪問。
loadFactor:負載因子,默認為 0.75。
threshold:擴容的閾值,等于 capacity * loadFactor
初始化Segment數組,該長度ssize是通過concurrencyLevel計算得出,需要能通過按位與的散列算法來定位數組的索引,故必須要保證Segment的長度為2的N次方,故concurrencyLevel為14、15、16,ssize都等于15,即容器鎖的格式也是16
初始化segment,initialCapacity為ConcurrentHashMap初始化容量,loadfactor是每個segment的負載因子
由于ConcurrentHashMap使用分段鎖Segment來保護不同段的數據,那么在插入和讀取元素的時候,必須先通過Wang/Jenkins hash的變種算法對元素的hashCode進行再次散列
那么為什么需要再次散列呢?
為了減少散列沖突,使得元素能夠均勻分布在不同的segment中,從而達到提高容器的存取效率,那么不再次散列會出現什么結果?
會導致不同數值它的散列值可能會相同,從而導致所有數值都存在同一個segment中,從而導致segment失去分段鎖的意義并且存取緩慢
如果進行再次散列,那么所有的數值都會散列開,之前的問題迎刃而解
ConcurrentHashMap高效的原因之一在于get操作不需要加鎖,因為它所有共享變量都定義成volatile,能夠保證在多線程之間的可見性,能夠被多線程同時讀,并且保證不會讀到過期值,并且由volatile修飾的共享變量均不需要回寫主內存
put操作時由于對共享變量進行了寫的操作,故必須加鎖,那么put方法首先會定位到segment(因為segment相當于裝載元素的桶),然后進行插入操作,操作分為2步:
1、在插入元素之前會先判斷是否需要擴容(所有容器都是需要判斷擴容),做法是如果capacity *loadFactor大于threshold則擴容,
2、擴容多少呢?首先會創建一個容量為原來2倍的數組,將原有元素進行散列后插入新的數組,為了高效,只會針對某個segment進行擴容
那這個就是負載因子的作用,這個操作和HashMap不一致,HashMap是在插入完之后進行判斷是否需要擴容,就會導致如果下次沒有插入數據,那么這個空間就浪費了,在jdk1.8還做了部分修改
size操作:就是為了統計segment的所有大小,那么segment的count是個全局變量是用volatile修飾,最快速的做法是,把所有的segment的count加起來,但是如果在這期間出現了修改操作,那么count的統計就不精確了,如果把segment的put、remove、clean全部鎖住,那么效率太低,目前的做法是,嘗試2次不鎖住segment來統計count,如果統計過程中出現容器變化,那么就加鎖,來統計,ConcurrentHashMap是如何知道統計過程中容器發生了變化,用modCount變量,在put、remove、clean方法里操作元素前會將modCount進行加1,比較前后變化
jdk1.8不再采用segment作為鎖的粒度,而是直接將HashEntry作為鎖,從而降低鎖的粒度、
jdk1.8使用內置鎖synchronized來代替重入鎖ReentrantLock
因為粒度降低了,在相對而言的低粒度加鎖方式,synchronized并不比ReentrantLock差,在粗粒度加鎖中ReentrantLock可能通過Condition來控制各個低粒度的邊界,更加的靈活,而在低粒度中,Condition的優勢就沒有了
JVM的開發團隊從來都沒有放棄synchronized,而且基于JVM的synchronized優化空間更大,使用內嵌的關鍵字比使用API更加自然
在大量的數據操作下,對于JVM的內存壓力,基于API的ReentrantLock會開銷更多的內存,雖然不是瓶頸,但是也是一個選擇依據
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77470.html
摘要:簡介是的線程安全版本,內部也是使用數組鏈表紅黑樹的結構來存儲元素。相比于同樣線程安全的來說,效率等各方面都有極大地提高。中的關鍵字,內部實現為監視器鎖,主要是通過對象監視器在對象頭中的字段來表明的。 簡介 ConcurrentHashMap是HashMap的線程安全版本,內部也是使用(數組 + 鏈表 + 紅黑樹)的結構來存儲元素。 相比于同樣線程安全的HashTable來說,效率等各方...
摘要:那么,如果之后不是簡單的操作,而是還有其它業務操作,之后才是,比如下面這樣,這該怎么辦呢其它業務操作這時候就沒辦法使用提供的方法了,只能業務自己來保證線程安全了,比如下面這樣其它業務操作這樣雖然不太友好,但是最起碼能保證業務邏輯是正確的。 刪除元素 刪除元素跟添加元素一樣,都是先找到元素所在的桶,然后采用分段鎖的思想鎖住整個桶,再進行操作。 public V remove(Objec...
摘要:最近準備面試,一談到基礎,大部分面試官上來就數據結構素質三連與區別,底層數據結構,為什么能保證線程安全。數組順序存儲,內存連續,查詢快,插入刪除效率稍微低,不過現在略有改善。而在開始,是由和的方式去實現高并發下的線程安全。 最近準備面試,一談到java基礎,大部分面試官上來就java數據結構素質三連:ArrayList與LinkedList區別,HashMap底層數據結構,Concur...
摘要:下面我來簡單總結一下的核心要點底層結構是散列表數組鏈表紅黑樹,這一點和是一樣的。是將所有的方法進行同步,效率低下。而作為一個高并發的容器,它是通過部分鎖定算法來進行實現線程安全的。 前言 聲明,本文用的是jdk1.8 前面章節回顧: Collection總覽 List集合就這么簡單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡單【源碼剖析】 LinkedHas...
摘要:總結中針對數組的訪問和賦值的意義應該是在于越過對數組操作的包裝,進而達到優化性能的目的。以上為拋磚引玉。。參考鏈接知乎請問數組的行為是如何實現的 在學習ConcurrentHashMap時發現,源碼中對table數組的元素進行操作時,使用了三個封裝好的原子操作方法,如下: /* ---------------- Table element access -------------- *...
閱讀 2586·2021-10-25 09:45
閱讀 1246·2021-10-14 09:43
閱讀 2304·2021-09-22 15:23
閱讀 1529·2021-09-22 14:58
閱讀 1939·2019-08-30 15:54
閱讀 3547·2019-08-30 13:00
閱讀 1361·2019-08-29 18:44
閱讀 1578·2019-08-29 16:59