2021-03-14 分類: 網(wǎng)站建設(shè)
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因?yàn)樗С植l(fā)操作,所以要復(fù)雜一些。
整個(gè) ConcurrentHashMap 由一個(gè)個(gè) Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會(huì)將其描述為分段鎖。注意,行文中,我很多地方用了“槽”來代表一個(gè) segment。
簡(jiǎn)單理解就是,ConcurrentHashMap 是一個(gè) Segment 數(shù)組,Segment 通過繼承 ReentrantLock 來進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個(gè) segment,這樣只要保證每個(gè) Segment 是線程安全的,也就實(shí)現(xiàn)了全局的線程安全。
concurrencyLevel:并行級(jí)別、并發(fā)數(shù)、Segment 數(shù),怎么翻譯不重要,理解它。默認(rèn)是 16,也就是說 ConcurrentHashMap 有 16 個(gè) Segments,所以理論上,這個(gè)時(shí)候,最多可以同時(shí)支持 16 個(gè)線程并發(fā)寫,只要它們的操作分別分布在不同的 Segment 上。這個(gè)值可以在初始化的時(shí)候設(shè)置為其他值,但是一旦初始化以后,它是不可以擴(kuò)容的。
再具體到每個(gè) Segment 內(nèi)部,其實(shí)每個(gè) Segment 很像之前介紹的 HashMap,不過它要保證線程安全,所以處理起來要麻煩些。
initialCapacity:初始容量,這個(gè)值指的是整個(gè) ConcurrentHashMap 的初始容量,實(shí)際操作的時(shí)候需要平均分給每個(gè) Segment。
loadFactor:負(fù)載因子,之前我們說了,Segment 數(shù)組不可以擴(kuò)容,所以這個(gè)負(fù)載因子是給每個(gè) Segment 內(nèi)部使用的。
初始化完成,我們得到了一個(gè) Segment 數(shù)組。
我們就當(dāng)是用 new ConcurrentHashMap() 無參構(gòu)造函數(shù)進(jìn)行初始化的,那么初始化完成后:
我們先看 put 的主流程,對(duì)于其中的一些關(guān)鍵細(xì)節(jié)操作,后面會(huì)進(jìn)行詳細(xì)介紹。
第一層皮很簡(jiǎn)單,根據(jù) hash 值很快就能找到相應(yīng)的 Segment,之后就是 Segment 內(nèi)部的 put 操作了。
Segment 內(nèi)部是由 數(shù)組+鏈表 組成的。
整體流程還是比較簡(jiǎn)單的,由于有獨(dú)占鎖的保護(hù),所以 segment 內(nèi)部的操作并不復(fù)雜。至于這里面的并發(fā)問題,我們稍后再進(jìn)行介紹。
到這里 put 操作就結(jié)束了,接下來,我們說一說其中幾步關(guān)鍵的操作。
ConcurrentHashMap 初始化的時(shí)候會(huì)初始化第一個(gè)槽 segment[0],對(duì)于其他槽來說,在插入第一個(gè)值的時(shí)候進(jìn)行初始化。
這里需要考慮并發(fā),因?yàn)楹芸赡軙?huì)有多個(gè)線程同時(shí)進(jìn)來初始化同一個(gè)槽 segment[k],不過只要有一個(gè)成功了就可以。
總的來說,ensureSegment(int k) 比較簡(jiǎn)單,對(duì)于并發(fā)操作使用 CAS 進(jìn)行控制。
我沒搞懂這里為什么要搞一個(gè) while 循環(huán),CAS 失敗不就代表有其他線程成功了嗎,為什么要再進(jìn)行判斷?
前面我們看到,在往某個(gè) segment 中 put 的時(shí)候,首先會(huì)調(diào)用 node = tryLock() ? null : scanAndLockForPut(key, hash, value),也就是說先進(jìn)行一次 tryLock() 快速獲取該 segment 的獨(dú)占鎖,如果失敗,那么進(jìn)入到 scanAndLockForPut 這個(gè)方法來獲取鎖。
下面我們來具體分析這個(gè)方法中是怎么控制加鎖的。
這個(gè)方法有兩個(gè)出口,一個(gè)是 tryLock() 成功了,循環(huán)終止,另一個(gè)就是重試次數(shù)超過了 MAX_SCAN_RETRIES,進(jìn)到 lock() 方法,此方法會(huì)阻塞等待,直到成功拿到獨(dú)占鎖。
這個(gè)方法就是看似復(fù)雜,但是其實(shí)就是做了一件事,那就是獲取該 segment 的獨(dú)占鎖,如果需要的話順便實(shí)例化了一下 node。
重復(fù)一下,segment 數(shù)組不能擴(kuò)容,擴(kuò)容是 segment 數(shù)組某個(gè)位置內(nèi)部的數(shù)組 HashEntry\[] 進(jìn)行擴(kuò)容,擴(kuò)容后,容量為原來的 2 倍。
首先,我們要回顧一下觸發(fā)擴(kuò)容的地方,put 的時(shí)候,如果判斷該值的插入會(huì)導(dǎo)致該 segment 的元素個(gè)數(shù)超過閾值,那么先進(jìn)行擴(kuò)容,再插值,讀者這個(gè)時(shí)候可以回去 put 方法看一眼。
該方法不需要考慮并發(fā),因?yàn)榈竭@里的時(shí)候,是持有該 segment 的獨(dú)占鎖的。
這里的擴(kuò)容比之前的 HashMap 要復(fù)雜一些,代碼難懂一點(diǎn)。上面有兩個(gè)挨著的 for 循環(huán),第一個(gè) for 有什么用呢?
仔細(xì)一看發(fā)現(xiàn),如果沒有第一個(gè) for 循環(huán),也是可以工作的,但是,這個(gè) for 循環(huán)下來,如果 lastRun 的后面還有比較多的節(jié)點(diǎn),那么這次就是值得的。因?yàn)槲覀冎恍枰寺?lastRun 前面的節(jié)點(diǎn),后面的一串節(jié)點(diǎn)跟著 lastRun 走就是了,不需要做任何操作。
我覺得 Doug Lea 的這個(gè)想法也是挺有意思的,不過比較壞的情況就是每次 lastRun 都是鏈表的最后一個(gè)元素或者很靠后的元素,那么這次遍歷就有點(diǎn)浪費(fèi)了。不過 Doug Lea 也說了,根據(jù)統(tǒng)計(jì),如果使用默認(rèn)的閾值,大約只有 1/6 的節(jié)點(diǎn)需要克隆。
相對(duì)于 put 來說,get 真的不要太簡(jiǎn)單。
現(xiàn)在我們已經(jīng)說完了 put 過程和 get 過程,我們可以看到 get 過程中是沒有加鎖的,那自然我們就需要去考慮并發(fā)問題。
添加節(jié)點(diǎn)的操作 put 和刪除節(jié)點(diǎn)的操作 remove 都是要加 segment 上的獨(dú)占鎖的,所以它們之間自然不會(huì)有問題,我們需要考慮的問題就是 get 的時(shí)候在同一個(gè) segment 中發(fā)生了 put 或 remove 操作。
remove 操作我們沒有分析源碼,所以這里說的讀者感興趣的話還是需要到源碼中去求實(shí)一下的。
get 操作需要遍歷鏈表,但是 remove 操作會(huì)”破壞”鏈表。
如果 remove 破壞的節(jié)點(diǎn) get 操作已經(jīng)過去了,那么這里不存在任何問題。
如果 remove 先破壞了一個(gè)節(jié)點(diǎn),分兩種情況考慮。 1、如果此節(jié)點(diǎn)是頭結(jié)點(diǎn),那么需要將頭結(jié)點(diǎn)的 next 設(shè)置為數(shù)組該位置的元素,table 雖然使用了 volatile 修飾,但是 volatile 并不能提供數(shù)組內(nèi)部操作的可見性保證,所以源碼中使用了 UNSAFE 來操作數(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 的。
本文名稱:concurrenthashmap你經(jīng)常用,但你可能并不了解
文章URL:http://www.rwnh.cn/news/105161.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、小程序開發(fā)、服務(wù)器托管、搜索引擎優(yōu)化、標(biāo)簽優(yōu)化、域名注冊(cè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容