成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

concurrenthashmap你經(jīng)常用,但你可能并不了解

2021-03-14    分類(lèi): 網(wǎng)站建設(shè)

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因?yàn)樗С植l(fā)操作,所以要復(fù)雜一些。

整個(gè) ConcurrentHashMap 由一個(gè)個(gè) Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會(huì)將其描述為分段鎖。注意,行文中,我很多地方用了“槽”來(lái)代表一個(gè) segment。

簡(jiǎn)單理解就是,ConcurrentHashMap 是一個(gè) Segment 數(shù)組,Segment 通過(guò)繼承 ReentrantLock 來(lái)進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個(gè) segment,這樣只要保證每個(gè) Segment 是線(xiàn)程安全的,也就實(shí)現(xiàn)了全局的線(xiàn)程安全。

concurrentha

concurrencyLevel:并行級(jí)別、并發(fā)數(shù)、Segment 數(shù),怎么翻譯不重要,理解它。默認(rèn)是 16,也就是說(shuō) ConcurrentHashMap 有 16 個(gè) Segments,所以理論上,這個(gè)時(shí)候,最多可以同時(shí)支持 16 個(gè)線(xiàn)程并發(fā)寫(xiě),只要它們的操作分別分布在不同的 Segment 上。這個(gè)值可以在初始化的時(shí)候設(shè)置為其他值,但是一旦初始化以后,它是不可以擴(kuò)容的。

再具體到每個(gè) Segment 內(nèi)部,其實(shí)每個(gè) Segment 很像之前介紹的 HashMap,不過(guò)它要保證線(xiàn)程安全,所以處理起來(lái)要麻煩些。

初始化

initialCapacity:初始容量,這個(gè)值指的是整個(gè) ConcurrentHashMap 的初始容量,實(shí)際操作的時(shí)候需要平均分給每個(gè) Segment。

loadFactor:負(fù)載因子,之前我們說(shuō)了,Segment 數(shù)組不可以擴(kuò)容,所以這個(gè)負(fù)載因子是給每個(gè) Segment 內(nèi)部使用的。

concurrentha

初始化完成,我們得到了一個(gè) Segment 數(shù)組。

我們就當(dāng)是用 new ConcurrentHashMap() 無(wú)參構(gòu)造函數(shù)進(jìn)行初始化的,那么初始化完成后:

  • Segment 數(shù)組長(zhǎng)度為 16,不可以擴(kuò)容
  • Segment[i] 的默認(rèn)大小為 2,負(fù)載因子是 0.75,得出初始閾值為 1.5,也就是以后插入第一個(gè)元素不會(huì)觸發(fā)擴(kuò)容,插入第二個(gè)會(huì)進(jìn)行第一次擴(kuò)容
  • 這里初始化了 segment[0],其他位置還是 null,至于為什么要初始化 segment[0],后面的代碼會(huì)介紹
  • 當(dāng)前 segmentShift 的值為 32 – 4 = 28,segmentMask 為 16 – 1 = 15,姑且把它們簡(jiǎn)單翻譯為移位數(shù)和掩碼,這兩個(gè)值馬上就會(huì)用到

put 過(guò)程分析

我們先看 put 的主流程,對(duì)于其中的一些關(guān)鍵細(xì)節(jié)操作,后面會(huì)進(jìn)行詳細(xì)介紹。

concurrentha

第一層皮很簡(jiǎn)單,根據(jù) hash 值很快就能找到相應(yīng)的 Segment,之后就是 Segment 內(nèi)部的 put 操作了。

Segment 內(nèi)部是由 數(shù)組+鏈表 組成的。

concurrentha

整體流程還是比較簡(jiǎn)單的,由于有獨(dú)占鎖的保護(hù),所以 segment 內(nèi)部的操作并不復(fù)雜。至于這里面的并發(fā)問(wèn)題,我們稍后再進(jìn)行介紹。

到這里 put 操作就結(jié)束了,接下來(lái),我們說(shuō)一說(shuō)其中幾步關(guān)鍵的操作。

初始化槽: ensureSegment

ConcurrentHashMap 初始化的時(shí)候會(huì)初始化第一個(gè)槽 segment[0],對(duì)于其他槽來(lái)說(shuō),在插入第一個(gè)值的時(shí)候進(jìn)行初始化。

這里需要考慮并發(fā),因?yàn)楹芸赡軙?huì)有多個(gè)線(xiàn)程同時(shí)進(jìn)來(lái)初始化同一個(gè)槽 segment[k],不過(guò)只要有一個(gè)成功了就可以。

concurrentha

總的來(lái)說(shuō),ensureSegment(int k) 比較簡(jiǎn)單,對(duì)于并發(fā)操作使用 CAS 進(jìn)行控制。

我沒(méi)搞懂這里為什么要搞一個(gè) while 循環(huán),CAS 失敗不就代表有其他線(xiàn)程成功了嗎,為什么要再進(jìn)行判斷?

獲取寫(xiě)入鎖: scanAndLockForPut

前面我們看到,在往某個(gè) segment 中 put 的時(shí)候,首先會(huì)調(diào)用 node = tryLock() ? null : scanAndLockForPut(key, hash, value),也就是說(shuō)先進(jìn)行一次 tryLock() 快速獲取該 segment 的獨(dú)占鎖,如果失敗,那么進(jìn)入到 scanAndLockForPut 這個(gè)方法來(lái)獲取鎖。

下面我們來(lái)具體分析這個(gè)方法中是怎么控制加鎖的。

concurrentha

這個(gè)方法有兩個(gè)出口,一個(gè)是 tryLock() 成功了,循環(huán)終止,另一個(gè)就是重試次數(shù)超過(guò)了 MAX_SCAN_RETRIES,進(jìn)到 lock() 方法,此方法會(huì)阻塞等待,直到成功拿到獨(dú)占鎖。

這個(gè)方法就是看似復(fù)雜,但是其實(shí)就是做了一件事,那就是獲取該 segment 的獨(dú)占鎖,如果需要的話(huà)順便實(shí)例化了一下 node。

擴(kuò)容: rehash

重復(fù)一下,segment 數(shù)組不能擴(kuò)容,擴(kuò)容是 segment 數(shù)組某個(gè)位置內(nèi)部的數(shù)組 HashEntry\[] 進(jìn)行擴(kuò)容,擴(kuò)容后,容量為原來(lái)的 2 倍。

首先,我們要回顧一下觸發(fā)擴(kuò)容的地方,put 的時(shí)候,如果判斷該值的插入會(huì)導(dǎo)致該 segment 的元素個(gè)數(shù)超過(guò)閾值,那么先進(jìn)行擴(kuò)容,再插值,讀者這個(gè)時(shí)候可以回去 put 方法看一眼。

該方法不需要考慮并發(fā),因?yàn)榈竭@里的時(shí)候,是持有該 segment 的獨(dú)占鎖的。

concurrentha

這里的擴(kuò)容比之前的 HashMap 要復(fù)雜一些,代碼難懂一點(diǎn)。上面有兩個(gè)挨著的 for 循環(huán),第一個(gè) for 有什么用呢?

仔細(xì)一看發(fā)現(xiàn),如果沒(méi)有第一個(gè) for 循環(huán),也是可以工作的,但是,這個(gè) for 循環(huán)下來(lái),如果 lastRun 的后面還有比較多的節(jié)點(diǎn),那么這次就是值得的。因?yàn)槲覀冎恍枰寺?lastRun 前面的節(jié)點(diǎn),后面的一串節(jié)點(diǎn)跟著 lastRun 走就是了,不需要做任何操作。

我覺(jué)得 Doug Lea 的這個(gè)想法也是挺有意思的,不過(guò)比較壞的情況就是每次 lastRun 都是鏈表的最后一個(gè)元素或者很靠后的元素,那么這次遍歷就有點(diǎn)浪費(fèi)了。不過(guò) Doug Lea 也說(shuō)了,根據(jù)統(tǒng)計(jì),如果使用默認(rèn)的閾值,大約只有 1/6 的節(jié)點(diǎn)需要克隆。

get 過(guò)程分析

相對(duì)于 put 來(lái)說(shuō),get 真的不要太簡(jiǎn)單。

  1. 計(jì)算 hash 值,找到 segment 數(shù)組中的具體位置,或我們前面用的“槽”
  2. 槽中也是一個(gè)數(shù)組,根據(jù) hash 找到數(shù)組中具體的位置
  3. 到這里是鏈表了,順著鏈表進(jìn)行查找即可
concurrentha

并發(fā)問(wèn)題分析

現(xiàn)在我們已經(jīng)說(shuō)完了 put 過(guò)程和 get 過(guò)程,我們可以看到 get 過(guò)程中是沒(méi)有加鎖的,那自然我們就需要去考慮并發(fā)問(wèn)題。

添加節(jié)點(diǎn)的操作 put 和刪除節(jié)點(diǎn)的操作 remove 都是要加 segment 上的獨(dú)占鎖的,所以它們之間自然不會(huì)有問(wèn)題,我們需要考慮的問(wèn)題就是 get 的時(shí)候在同一個(gè) segment 中發(fā)生了 put 或 remove 操作。

  1. put 操作的線(xiàn)程安全性。
    • 初始化槽,這個(gè)我們之前就說(shuō)過(guò)了,使用了 CAS 來(lái)初始化 Segment 中的數(shù)組。
    • 添加節(jié)點(diǎn)到鏈表的操作是插入到表頭的,所以,如果這個(gè)時(shí)候 get 操作在鏈表遍歷的過(guò)程已經(jīng)到了中間,是不會(huì)影響的。當(dāng)然,另一個(gè)并發(fā)問(wèn)題就是 get 操作在 put 之后,需要保證剛剛插入表頭的節(jié)點(diǎn)被讀取,這個(gè)依賴(lài)于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject。
    • 擴(kuò)容。擴(kuò)容是新創(chuàng)建了數(shù)組,然后進(jìn)行遷移數(shù)據(jù),最后面將 newTable 設(shè)置給屬性 table。所以,如果 get 操作此時(shí)也在進(jìn)行,那么也沒(méi)關(guān)系,如果 get 先行,那么就是在舊的 table 上做查詢(xún)操作;而 put 先行,那么 put 操作的可見(jiàn)性保證就是 table 使用了 volatile 關(guān)鍵字。
  2. remove 操作的線(xiàn)程安全性。

    remove 操作我們沒(méi)有分析源碼,所以這里說(shuō)的讀者感興趣的話(huà)還是需要到源碼中去求實(shí)一下的。

    get 操作需要遍歷鏈表,但是 remove 操作會(huì)”破壞”鏈表。

    如果 remove 破壞的節(jié)點(diǎn) get 操作已經(jīng)過(guò)去了,那么這里不存在任何問(wèn)題。

    如果 remove 先破壞了一個(gè)節(jié)點(diǎn),分兩種情況考慮。 1、如果此節(jié)點(diǎn)是頭結(jié)點(diǎn),那么需要將頭結(jié)點(diǎn)的 next 設(shè)置為數(shù)組該位置的元素,table 雖然使用了 volatile 修飾,但是 volatile 并不能提供數(shù)組內(nèi)部操作的可見(jiàn)性保證,所以源碼中使用了 UNSAFE 來(lái)操作數(shù)組,請(qǐng)看方法 setEntryAt。2、如果要?jiǎng)h除的節(jié)點(diǎn)不是頭結(jié)點(diǎn),它會(huì)將要?jiǎng)h除節(jié)點(diǎn)的后繼節(jié)點(diǎn)接到前驅(qū)節(jié)點(diǎn)中,這里的并發(fā)保證就是 next 屬性是 volatile 的。

本文名稱(chēng):concurrenthashmap你經(jīng)常用,但你可能并不了解
文章URL:http://jinyejixie.com/news/105161.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、小程序開(kāi)發(fā)、服務(wù)器托管、搜索引擎優(yōu)化、標(biāo)簽優(yōu)化、域名注冊(cè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)
长乐市| 绥宁县| 旌德县| 确山县| 葫芦岛市| 普宁市| 承德市| 富阳市| 丰顺县| 华容县| 新沂市| 法库县| 平顺县| 花莲县| 开平市| 伊川县| 武威市| 福泉市| 淳安县| 丁青县| 新干县| 洛南县| 五寨县| 抚顺市| 泰宁县| 潮安县| 曲周县| 黑龙江省| 桂阳县| 阿尔山市| 茶陵县| 都江堰市| 永昌县| 彩票| 新乐市| 河津市| 吉木乃县| 泾川县| 革吉县| 广元市| 石狮市|