徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)

mikechen的網際網路架構發表於2022-02-16

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構

ConcurrentHashMap 的實現原理基本都是大廠面試必考內容, 深入探討HashMap的底層結構、原理、擴容機制,深入談過hashmap的實現原理以及在JDK 1.8的實現區別,今天主要談ConcurrentHashMap的實現原理,以及在JDK1.7和1.8的區別。

為了徹底搞清楚ConcurrentHashMap的實現機制,我會先從它的底層資料實現:雜湊表談起。

內容目錄:

1.雜湊表

2.ConcurrentHashMap與HashMap、HashTable的區別

3.ConcurrentHashMap在JDK1.7和JDK1.8版本的區別

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構

一:雜湊表

1.介紹

雜湊表就是一種以 鍵-值(key-indexed) 儲存資料的結構,我們只要輸入待查詢的值即key,即可查詢到其對應的值。

雜湊的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序陣列來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴充套件到可以處理更加複雜的型別的鍵。

2.鏈式雜湊表

鏈式雜湊表從根本上說是由一組連結串列構成。每個連結串列都可以看做是一個“桶”,我們將所有的元素透過雜湊的方式放到具體的不同的桶中。插入元素時,首先將其鍵傳入一個雜湊函式(該過程稱為雜湊鍵),函式透過雜湊的方式告知元素屬於哪個“桶”,然後在相應的連結串列頭插入元素。查詢或刪除元素時,用同們的方式先找到元素的“桶”,然後遍歷相應的連結串列,直到發現我們想要的元素。因為每個“桶”都是一個連結串列,所以鏈式雜湊表並不限制包含元素的個數。然而,如果表變得太大,它的效能將會降低。

3.應用場景

我們熟知的快取技術(比如redis、memcached)的核心其實就是在記憶體中維護一張巨大的雜湊表,還有大家熟知的HashMap、ConcurrentHashMap等的應用。

 

二:ConcurrentHashMap與HashMap等的區別

1.HashMap

我們知道HashMap是執行緒不安全的,在多執行緒環境下,使用Hashmap進行put操作會引起死迴圈,導致CPU利用率接近100%,所以 在併發情況下不能使用HashMap

2.HashTable

HashTable和HashMap的實現原理幾乎一樣,差別無非是

  • HashTable不允許key和value為null
  • HashTable是執行緒安全的

但是HashTable執行緒安全的策略實現代價卻太大了,簡單粗暴,get/put所有相關操作都是synchronized的,這相當於 給整個雜湊表加了一把大鎖,如下圖所示:

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構

多執行緒訪問時候,只要有一個執行緒訪問或操作該物件,那其他執行緒只能阻塞,相當於將所有的操作 序列化,在競爭激烈的併發場景中效能就會非常差。

3.ConcurrentHashMap

主要就是為了應對hashmap在併發環境下不安全而誕生的,ConcurrentHashMap的設計與實現非常精巧,大量的利用了volatile,final,CAS等lock-free技術來減少鎖競爭對於效能的影響。

我們都知道Map一般都是 陣列+連結串列結構(JDK1.8該為陣列+紅黑樹)。

ConcurrentHashMap避免了對全域性加鎖改成了區域性加鎖操作,這樣就極大地提高了併發環境下的操作速度,由於ConcurrentHashMap在JDK1.7和1.8中的實現非常不同,接下來我們談談JDK在1.7和1.8中的區別。

 

三:JDK1.7版本的ConcurrentHashMap的實現原理

在JDK1.7中ConcurrentHashMap採用了 陣列+Segment+分段鎖的方式實現。

1.Segment(分段鎖)

ConcurrentHashMap中的 分段鎖稱為Segment,它即類似於HashMap的結構,即內部擁有一個Entry陣列,陣列中的每個元素又是一個連結串列,同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

2.內部結構

ConcurrentHashMap使用分段鎖技術,將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問,能夠實現真正的併發訪問。如下圖是ConcurrentHashMap的內部結構圖:

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構
面的結構我們可以瞭解到,ConcurrentHashMap定位一個元素的過程需要進行兩次Hash操作。

第一次Hash定位到Segment,第二次Hash定位到元素所在的連結串列的頭部。

3.該結構的優劣勢

壞處

這一種結構的帶來的副作用是Hash的過程要比普通的HashMap要長

好處

寫操作的時候可以只對元素所在的Segment進行加鎖即可,不會影響到其他的Segment,這樣,在最理想的情況下,ConcurrentHashMap可以最高同時支援Segment數量大小的寫操作(剛好這些寫操作都非常平均地分佈在所有的Segment上)。

所以,透過這一種結構,ConcurrentHashMap的併發能力可以大大的提高。

 

四:JDK1.8版本的ConcurrentHashMap的實現原理

JDK8中ConcurrentHashMap參考了JDK8 HashMap的實現,採用了 陣列+連結串列+紅黑樹的實現方式來設計,如下圖所示:

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構

內部大量採用CAS操作,這裡我簡要介紹下CAS。

CAS是compare and swap的縮寫,即我們所說的比較交換。cas是一種基於鎖的操作,而且是樂觀鎖。在java中鎖分為樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個之前獲得鎖的執行緒釋放鎖之後,下一個執行緒才可以訪問。而樂觀鎖採取了一種寬泛的態度,透過某種方式不加鎖來處理資源,比如透過給記錄加version來獲取資料,效能較悲觀鎖有很大的提高。

CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。如果記憶體地址裡面的值和A的值是一樣的,那麼就將記憶體裡面的值更新成B。CAS是透過無限迴圈來獲取資料的,若果在第一輪迴圈中,a執行緒獲取地址裡面的值被b執行緒修改了,那麼a執行緒需要自旋,到下次迴圈才有可能機會執行。

JDK8中徹底 放棄了Segment轉而採用的是Node,其設計思想也不再是JDK1.7中的分段鎖思想。

Node:儲存key,value及key的hash值的資料結構。其中value和next都用volatile修飾,保證併發的可見性。

class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; //… 省略部分程式碼 }

Java8 ConcurrentHashMap結構基本上和Java8的HashMap一樣,不過保證執行緒安全性。

在JDK8中ConcurrentHashMap的結構,由於引入了紅黑樹,使得ConcurrentHashMap的實現非常複雜,我們都知道,紅黑樹是一種效能非常好的二叉查詢樹,其查詢效能為O(logN),但是其實現過程也非常複雜,而且可讀性也非常差,Doug
Lea的思維能力確實不是一般人能比的,早期完全採用連結串列結構時Map的查詢時間複雜度為O(N),JDK8中ConcurrentHashMap在連結串列的長度大於某個閾值的時候會將連結串列轉換成紅黑樹進一步提高其查詢效能。

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)-mikechen的網際網路架構

 

五:ConcurrentHashMap總結

其實可以看出JDK1.8版本的ConcurrentHashMap的資料結構已經接近HashMap,相對而言,ConcurrentHashMap只是增加了同步的操作來控制併發,從JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+紅黑樹。

1.資料結構:取消了Segment分段鎖的資料結構,取而代之的是陣列+連結串列+紅黑樹的結構。

2.保證執行緒安全機制:JDK1.7採用segment的分段鎖機制實現執行緒安全,其中segment繼承自ReentrantLock。JDK1.8採用CAS+Synchronized保證執行緒安全。

3.鎖的粒度:原來是對需要進行資料操作的Segment加鎖,現調整為對每個陣列元素加鎖(Node)。

4.連結串列轉化為紅黑樹:定位結點的hash演算法簡化會帶來弊端,Hash衝突加劇,因此在連結串列節點數量大於8時,會將連結串列轉化為紅黑樹進行儲存。

5.查詢時間複雜度:從原來的遍歷連結串列O(n),變成遍歷紅黑樹O(logN)。

關於作者:mikechen ,十餘年BAT架構經驗,資深技術專家,曾任職阿里、淘寶、百度。

歡迎關注個人公眾號:    mikechen的網際網路架構,十餘年BAT架構經驗傾囊相授!

在公眾號選單欄對話方塊回覆【    架構】關鍵詞,即可檢視我    原創的300期+BAT架構技術系列文章與1000+大廠面試題答案合集。

徹底搞清楚ConcurrentHashMap的實現原理(含JDK1.7和JDK1.8的區別)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011997/viewspace-2855873/,如需轉載,請註明出處,否則將追究法律責任。

相關文章