ConcurrentHashMap 併發讀寫,用了什麼黑科技

Photo By Instagram love_loyi

昨天的問題

ConcurrentHashMap 在我們平時編碼的過程中不是很常用,但是它在 Java 基礎面試中是也是一道必考題,通常會拿出來和 HashMap 做比較,所以我們必須要掌握它。那麼,你知道 ConcurrentHashMap 是如何來實現一個支持併發讀寫的高效哈希表呢?

我的答案

ConcurrentHashMap 同 HashMap 一樣,都是 Map 接口的子類。不同之處在於 HashMap 是非線程安全的,如果想要在多線程環境下使用必須對它的操作添加互斥鎖。而 ConcurrentHashMap 爲併發而生,可以在多線程環境下隨便使用而不用開發人員去添加互斥鎖。那麼 ConcurrentHashMap 是如何實現線程安全的特性呢,接下來我們就從它的 put 方法說開來,對比 HashMap 的 put 操作來一步步揭開它的神祕面紗。

1.獲取哈希值

同 HashMap 一樣,ConcurrentHashMap 底層也是使用一個 Node 的數組來存儲數據。因此第一步也是獲取要存儲元素的哈希值。與 HashMap 不同的是,ConcurrentHashMap 不支持 null 和 null 值。

2.確定 hash 槽

這一步就不多說了,同 HashMap 基本一致。

3.初始化底層數組存儲

拿到 hash 槽以後就是存儲元素,但是首次 put 元素時候會觸發初始化底層數組操作,在 HashMap 中這一步操作是很簡單的,因爲是單線程操作直接初始化就可以了。但是在 ConcurrentHashMap 中就需要考慮併發問題了,因爲有可能有多個線程同時 put 元素。這裏就用到了我們之前文章中介紹的無鎖編程利器 volatile 和 CAS 原子操作。每個線程初始化數組之前都會先獲取到 volatile 修飾的sizeCtl 變量,只有設置了這個變量的值纔可以初始化數組,同時數組也是由 volatile 修飾的,以便修改後能被其他線程及時察覺。不知道 volatile 和 CAS 算法的同學可以參考往期的文章 ReentranLock 實現原理居然是這樣?具體初始化代碼可參考 ConcurrentHashMap 的 initTable 方法。

3.插入元素

數組初始化結束後就可以開心的插入元素了,插入數組元素又分了如下 3 個情況:

3.1當前槽中沒有元素

這種情況最爲簡單,直接調用 Unsafe 封裝好的 CAS 方法插入設置元素即可,成功則罷,不成功的話會轉變爲下個情況。

3.2槽中已有元素

操作已有元素也有 2 個情況:

3.2.1槽中元素正在被遷移(ConcurrentHashMap 擴容操作)

如果當前槽的元素正在被擴容到新的數組當中,那麼當前線程要幫忙遷移擴容。

3.2.3槽中元素處於普通狀態

這個時候就是和 HashMap 類似的 put 操作,特殊之處在於 ConcurrentHashMap 需要在此處加鎖,因爲可能當前有多個線程都想往這個槽上添加元素。這裏使用的 synchronized 鎖,鎖對象爲當前槽的第一個元素。鎖住以後的操作就和 HashMap 類似了,如果槽中依然是列表形態,那麼將當前元素包裝爲 Node 添加到隊列尾部,插入元素結束後如果發現列表元素達到了 8, 則會將列表轉換爲二叉樹(注意:插入元素後會釋放鎖,若發現要轉換爲二叉樹,則會重新進行加鎖操作)。如果槽中元素已經轉換爲了平衡二叉樹,那麼將元素封裝爲 TreeNode 插入到樹中。

4.擴容

在插入元素的末尾,ConcurrentHashMap 會計算當前容器內的元素,然後決定是否需要擴容。擴容這一部分涉及到了比較複雜的運算,後面我們會單獨出一遍文章來探討分析,敬請關注。

至此相信你已經對 ConcurrentHashMap 可以支持併發的原理有了大致的瞭解,它主要依靠了 volatile 和 CAS 操作,再加上 synchronized 即實現了一個支持併發的哈希表。瞭解了以上的內容和我們後面將要討論的擴容話題基本可以對付面試了,當然啦 ConcurrentHashMap 的源碼複雜程度遠遠高於 HashMap,對源碼有興趣的同學可以繼續努力啃一啃,相信肯定會有更大的收穫。

以上即爲昨天的問題的答案,小夥伴們對這個答案是否滿意呢?歡迎留言和我討論。

又要到年末了,你是不是又悄咪咪的開始看機會啦。爲了廣大小夥伴能充足電量,能順利通過 BAT 的面試官無情三連炮,我特意推出大型刷題節目。每天一道題目,第二天給答案,前一天給小夥伴們獨立思考的機會。

點下“在看”,鼓勵一下?

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