ConcurrentHashMap 併發擴容騷操作

上期問題
少年,老衲看你骨骼清奇,眉宇之間透露着一股王者的氣息。來吧,跟我講講 ConcurrentHashMap 是如何進行管理它的容量的,也就是當我們調用它的 size 方法的時候發生了什麼故事?(前面我們介紹了 ConcurrentHashMap 的實現原理,但是擴容和容量大小留了個小尾巴,今天來割一下這個小尾巴,嘿嘿)

我的答案
畢竟是要支持併發,ConcurrentHashMap 的擴容操作比較複雜,我們將從以下幾點來帶討論一下它的擴容。

觸發擴容

  1. 添加新元素後,元素個數達到擴容閾值觸發擴容。

  2. 調用 putAll 方法,發現容量不足以容納所有元素時候觸發擴容。

  3. 某個槽內的鏈表長度達到 8,但是數組長度小於 64 時候觸發擴容。

統計元素個數

觸發後面 2 點沒啥問題,但是第 1 點中有個小問題,它是如何統計元素的個數呢?它採用和 LongAdder類似的分散熱點數據的解決思路,不知道 LongAdder 的小夥伴可以參考往期文章 還在用 AtomicLong?是時候瞭解一下 LongAdder 了。ConcurrentHashMap 內部定義了一個 CounterCell 的類,它同樣被 Contended 修飾防止 volatile 帶來的僞共享問題,僞共享不瞭解可以參考往期文章 面試官問我 volatile 是否存在僞共享問題?我懵逼了。內部實例化了一個 CounterCell 的數組來記錄元素的個數,每當線程 put 一個元素到容器中,線程會被映射到一個 CounterCell 的一個元素上面採用 CAS 算法進行加 1 操作,當然如果當前 CounterCell 上已經有線程在操作,或者併發量比較小的話會直接將加 1 累加到 BASECOUNT 上面。

就是依據這樣的策略,容器的元素的個數就會遊刃有餘的計算出來,當需要獲取當前容器元素個數的時候,直接將 CounterCell 數據的每個元素值加起來再加上 BASECOUNT 的值就是當前容器中的元素個數。

擴容

上面我們知道了觸發擴容的條件爲元素個數達到閾值開始擴容,然後也知道了它是如何統計元素的個數的,接下來就看看擴容的運行機制。

首先每個線程承擔不小於 16 個槽中的元素的擴容,然後從右向左劃分 16 個槽給當前線程去遷移,每當開始遷移一個槽中的元素的時候,線程會鎖住當前槽中列表的頭元素,假設這時候正好有 get 請求過來會仍舊在舊的列表中訪問,如果是插入、修改、刪除、合併、compute 等操作時遇到 ForwardingNode,當前線程會加入擴容大軍幫忙一起擴容,擴容結束後再做元素的更新操作。

總結

總結一下,對於 ConcurrentHashMap 的擴容我們需要明確如上三點就可以了,擴容觸發的時機、元素個數的計算以及具體擴容過程中是如何堅持對外提供服務的就可以了。

如上即爲 ConcurrentHashMap 擴容操作的簡單介紹,實際 JDK 裏面的擴容源碼非常複雜,如果有小夥伴對源碼感興趣的話,本毛毛蟲找到一篇不錯的源碼分析文章大家可以參考一下 https://blog.csdn.net/ZOKEKAI/article/details/90051567 。這篇相當於 ConcurrentHashMap 的續集,對前序感興趣的可以參考往期文章 ConcurrentHashMap 併發讀寫,用了什麼黑科技。

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

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

在這裏插入圖片描述

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