小馬源碼_Java互聯網架構-重新認識Java8-HashMap-不一樣的源碼解讀

歡迎關注頭條號:java小馬哥

周一至周日早九點半!下午三點半!精品技術文章準時送上!!!

精品學習資料獲取通道,參見文末

74bea48239775db0e1b547c7dc7a9585.png

看源碼前我們必須先知道一下ConcurrentHashMap的基本結構。ConcurrentHashMap是采用分段鎖來進行并發控制的。

其中有一個內部類為Segment類用來表示鎖。而Segment類里又有一個HashEntry[]數組,這個數組才是真正用

來存放我們的key-value的。

大概為如下圖結構。一個Segment數組,而Segment數組每個元素為一個HashEntry數組

b946fcfba366a3368c729c60a3306173.png

看源碼前我們還必須了解的幾個默認的常量值:

DEFAULT_INITIAL_CAPACITY = 16 容器默認容量為16

DEFAULT_LOAD_FACTOR = 0.75f 默認擴容因子是0.75

DEFAULT_CONCURRENCY_LEVEL = 16 默認并發度是16

MAXIMUM_CAPACITY = 1 << 30 容器最大容量為1073741824

MIN_SEGMENT_TABLE_CAPACITY = 2 段的最小大小

MAX_SEGMENTS = 1 << 16 段的最大大小

RETRIES_BEFORE_LOCK = 2 通過不獲取鎖的方式嘗試獲取size的次數

以上以及默認值是ConcurrentHashMap中定義好的,下面我們很多地方會用到他們。

先從初始化開始說起

通過我們使用ConcurrentHashMap都是通過 ConcurrentHashMap map = new ConcurrentHashMap<>();的方式

我們點進去跟蹤下源碼

cdf34b8760cfa984b64271f4391b3c44.gif

/**

* Creates a new, empty map with a default initial capacity (16),

* load factor (0.75) and concurrencyLevel (16).

*/

public ConcurrentHashMap() {

this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);

}

cdf34b8760cfa984b64271f4391b3c44.gif

可以看到,默認無參構造函數內調用了另一個帶參構造函數,而這個構造函數也就是不管你初始化時傳進來什么參數,最終都會跳到那個帶參構造函數。

點進去看看這個帶參構造函數實現了什么功能

cdf34b8760cfa984b64271f4391b3c44.gif

public ConcurrentHashMap(int initialCapacity,

float loadFactor, int concurrencyLevel) {

if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)

throw new IllegalArgumentException();

if (concurrencyLevel > MAX_SEGMENTS)

concurrencyLevel = MAX_SEGMENTS;

// Find power-of-two sizes best matching arguments

int sshift = 0;

int ssize = 1;

while (ssize < concurrencyLevel) {

++sshift;

ssize <<= 1;

}

this.segmentShift = 32 - sshift;

this.segmentMask = ssize - 1;

if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY;

int c = initialCapacity / ssize;

if (c * ssize < initialCapacity)

++c;

int cap = MIN_SEGMENT_TABLE_CAPACITY;

while (cap < c)

cap <<= 1;

// create segments and segments[0]

Segment s0 =

new Segment(loadFactor, (int)(cap * loadFactor),

(HashEntry[])new HashEntry[cap]);

Segment[] ss = (Segment[])new Segment[ssize];

UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]

this.segments = ss;

}

cdf34b8760cfa984b64271f4391b3c44.gif

我們看到該構造函數一共有三個參數,分別是容器的初始化大小、負載因子、并發度,這三個參數如果我們new 一個ConcurrentHashMap時沒有指定,

那么將會采用默認的參數,也就是我們本文開始說的那幾個常量值。

在這里我對這三個參數做下解釋。容器初始化大小是整個map的容量。負載因子是用來計算每個segment里的HashEntry數組擴容時的閾值的。并發度是

用來設置segment數組的長度的。

6161dfc8da7e6d124efc00fb22821bf1.png

開頭這兩個if沒什么好說的。就是用來判斷我們傳進來的參數的正確性。當負載因子,初始容量和并發度不按照規范來時會拋出算術異常。第二個if時當傳進來的

并發度大于最大段大小的時候,就將其設置為最大段大小。

25a921e2ebf1987c3d4775c39eb1232f.png

這段就比較有意思了。由于segment數組要求長度必須為2的n次方,當我們傳進來的并發度不是2的n次方時會計算出一個最接近它的2的n次方值

比如如何我們傳進來的并發度為14 15那么通過計算segment數組長度就是16。在上圖中我們可以看到兩個局部變量ssize和sshift,在循環中如果ssize小于

并發度就將其二進制左移一位,即乘2。因此ssize就是用來保存我們計算出來的最接近并發度的2的n次方值。而ssfhit是用來計算偏移量的。在這里我們又

要說兩個很重要的全局常量。segmentMask和segmentShift。其中segmentMask為ssize - 1,由于ssize為2的倍數。那么segmentMask就是奇數。化為

二進制就是全1,而segmentShift為32 - sshift大小。32是key值經過再hash求出來的值的二進制位。segmentMask和segmentShift是用來定位當前元素

在segment數組那個位置,和在HashEntry數組的哪個位置,后面我們會詳細說說怎么算的。

b6514f09bf3fe0ee8e2eb5e6ab58455b.png

這一段代碼就是用來確定每個segment里面的hashentry的一些參數和初始化segment數組了。第一個if是防止我們設置的初始化

容量大于最大容量。而c是用來計算每個hashentry數組的容量。由于每個hashentry數組容量也需要為2的n次方,因此這里也需要

一個cap和循環來計算一個2的n次方值,方法和上面一樣。這里計算出來的cap值就是最終hashentry數組實際的大小了。

初始化就做了這些工作了。

那么我們在說說最簡單的get方法。

get方法就需要用到定位我們的元素了。而定位元素就需要我們上面初始化時設置好的兩個值:segmentMask和segmentShift

上面說了,并發度默認值為16,那么ssize也為16,因此segmentMask為15.由于ssize二進制往左移了4位,那么sshift就是4,

segmentShift就是32-4=28.下面我們就用segmentMask=15,segmentShift為28來說說怎么確定元素位置的。

在這里我們要說下hash值,這里的hash值不是key的hashcode值,而是經過再hash確定下來的一個hash值,目的是為了減少hash沖突。

hash值二進制為32位。

113031642ead9f82de2b397ddb6e130d.png

上圖兩個紅框就是分別確定segment數組中的位置和hashentry數組中的位置。

我們可以看到確定segment數組是采用 (h >>> segmentShift) & segmentMask,其中h為再hash過的hash值。將32為的hash值往右移segmentShift位。這里我們假設移了28位。

而segmentMask為15,就是4位都為一的二進制。將高4位與segmentMask相與會等到一個小于16的值,就是當前元素再的segment位置。

確定了所屬的segment后。就要確認在的hashentry位置了。通過第二個紅框處,我們可以看到確定hashentry的位置沒有使用上面兩個值了。而是直接使用當前hashentry數組的長度減一

和hash值想與。通過兩種不同的算法分別定位segment和hashenrty可以保證元素在segment數組和hashentry數組里面都散列開了。

Put方法

cdf34b8760cfa984b64271f4391b3c44.gif

public V put(K key, V value) {

Segment s;

if (value == null)

throw new NullPointerException();

int hash = hash(key);

int j = (hash >>> segmentShift) & segmentMask;

if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck

(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment

s = ensureSegment(j);

return s.put(key, hash, value, false);

}

cdf34b8760cfa984b64271f4391b3c44.gif
cdf34b8760cfa984b64271f4391b3c44.gif

final V put(K key, int hash, V value, boolean onlyIfAbsent) {

HashEntry node = tryLock() ? null :

scanAndLockForPut(key, hash, value);

V oldValue;

try {

HashEntry[] tab = table;

int index = (tab.length - 1) & hash;

HashEntry first = entryAt(tab, index);

for (HashEntry e = first;;) {

if (e != null) {

K k;

if ((k = e.key) == key ||

(e.hash == hash && key.equals(k))) {

oldValue = e.value;

if (!onlyIfAbsent) {

e.value = value;

++modCount;

}

break;

}

e = e.next;

}

else {

if (node != null)

node.setNext(first);

else

node = new HashEntry(hash, key, value, first);

int c = count + 1;

if (c > threshold && tab.length < MAXIMUM_CAPACITY)

rehash(node);

else

setEntryAt(tab, index, node);

++modCount;

count = c;

oldValue = null;

break;

}

}

} finally {

unlock();

}

return oldValue;

}

cdf34b8760cfa984b64271f4391b3c44.gif

上面兩片代碼就是put一個元素的過程。由于Put方法里需要對共享變量進行寫入操作,因此為了安全,需要在操作共享變量時加鎖。put時先定位到segment,然后在segment里及逆行擦汗如操作。

插入有兩個步驟,第一步判斷是否需要對segment里的hashenrty數組進行擴容。第二步是定位添加元素的位置,然后將其放在hashenrty數組里。

我們先說說擴容。

d1013d071e6a9c07c7c45ae2bd9d5815.png

在插入元素的時候會先判斷segment里面的hashenrty數組是否超過容量threshold。這個容量是我們剛開始初始化hashenrty數組時采用容量大小和負載因子計算出來的。

如果超過這個閾值(threshold)那么就會進行擴容。擴容括的時當前hashenrty而不是整個map。

如何擴容

擴容的時候會先創建一個容量是原來兩個容量大小的數組,然后將原數組里的元素進行再散列后插入到新的數組里。

Size方法

由于map里的元素是遍布所有hashenrty的。因此統計size的時候需要統計每個hashenrty的大小。由于是并發環境下,可能出現有線程在插入或者刪除的情況。因此會出現

錯誤。我們能想到的就是使用size方法時把所有的segment的put,remove和clean方法都鎖起來。但是這種方法時很低效的。因此concurrenthashmap采用了以下辦法:

先嘗試2次通過不加鎖的方式來統計各個segment大小,如果統計的過程中,容器的count發生了變化,再采用加鎖的方式來統計所有segment的大小。

concurrenthashmap時使用modcount變量來判斷再統計的時候容器是否放生了變化。在put、remove、clean方法里操作數據前都會將辯能力modCount進行加一,那么在統計

size千后比較modCount是否發生變化,就可以知道容器大小是否發生變化了。

封面圖源網絡,侵權刪除)

私信頭條號,發送:“資料”,獲取更多“秘制” 精品學習資料

如有收獲,請幫忙轉發,您的鼓勵是作者最大的動力,謝謝!

一大波微服務、分布式、高并發、高可用的原創系列文章正在路上,

歡迎關注頭條號:java小馬哥

周一至周日早九點半!下午三點半!精品技術文章準時送上!!!

十余年BAT架構經驗傾囊相授

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532935.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532935.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532935.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安裝默認報表服務器虛擬目錄,報表服務器虛擬目錄(Reporting Services 配置)

報表服務器虛擬目錄(Reporting Services 配置)12/15/2008本文內容使用“報表服務器虛擬目錄”頁可以配置報表服務器的虛擬目錄。用于訪問報表服務器 Web 服務的 URL 將包含該虛擬目錄名稱。完整的 URL 包括前綴(http:// 或 https://)、服務器名稱和虛擬目錄。服務器名稱可能是內…

小程序向webview傳參_獨家 | 支付寶小程序向個人開發者開放公測

基于興趣和周圍小群體開發的個人小程序&#xff0c;才是為支付寶提供更加多樣化的生活服務場景的來源。文 | Tech星球 (微信ID&#xff1a;tech618) 尹非凡、劉寧寧2月26日&#xff0c;Tech星球(微信ID&#xff1a;tech618) 獨家獲悉&#xff0c;支付寶小程序今日正式面向個人…

原神服務器維護后抽獎池會更新嗎,原神:更新維護一小時,補償60原石,玩家祈求多維護幾天!...

10月21號&#xff0c;原神社區發布公告&#xff0c;游戲將會在10月22號7點至11點進行停服維護&#xff0c;所有玩家在這個時間段將無法進入游戲。而作為補償&#xff0c;官方會贈送5級以上的玩家240原石(停服一小時送60原石)。這是偷偷的更新嗎&#xff1f;官方并沒有說更新內容…

涉及子模塊_COMSOL Multiphysics 5.6 RF模塊更新詳解

業界領先的多物理場仿真、App 設計與部署的軟件解決方案提供商COMSOL 公司發布了全新的COMSOL Multiphysics 軟件5.6 版本。新版本為多核和集群計算提供了計算速度更快且內存需求更低的求解器、更加高效的CAD 裝配處理功能、仿真App 布局模板&#xff0c;以及一系列包括剪裁平面…

系統參數shell服務器,shell 調用遠程服務器shell

shell 調用遠程服務器shell 內容精選換一換流程定義文件描述業務邏輯的XML文件&#xff0c;包括workflow.xml、coordinator.xml、bundle.xml三類&#xff0c;最終由Oozie引擎解析并執行。描述業務邏輯的XML文件&#xff0c;包括workflow.xml、coordinator.xml、bundle.xml三類&…

endnote國標_Citavi 與 Endnote 在 Word 插入引用,哪個更適合你?

前言&#xff1a;不黑、不吹&#xff0c;客觀討論&#xff0c;如有補充請留言&#xff0c;我們一定完善內容。我們先看下兩者在 Word 界面的顯示截圖&#xff1a;Endnote &#xff1a;&#xff08;看起來很簡潔&#xff09;Citavi &#xff1a;&#xff08;看起來功能多一些&am…

思科服務器如何修改啟動項,思科配置tftp服務器

思科配置tftp服務器 內容精選換一換使用mount命令掛載文件系統到云服務器&#xff0c;云服務器系統提示timed out。原因1&#xff1a;網絡狀態不穩定。原因2&#xff1a;網絡連接異常。原因3&#xff1a;云服務器DNS配置錯誤&#xff0c;導致解析不到文件系統的域名&#xff0c…

社保費客戶端顯示服務器連接異常,社保費客戶端登錄服務器異常

社保費客戶端登錄服務器異常 內容精選換一換本章節指導您使用MongoDB客戶端&#xff0c;通過彈性云服務器內網方式連接GaussDB(for Mongo)集群實例。操作系統使用場景&#xff1a;彈性云服務器的操作系統以Linux為例&#xff0c;客戶端本地使用的計算機系統以Windows為例。目標…

雙繼承_在Python中使用雙下劃線防止類屬性被覆蓋!

在使用Python編寫面向對象的代碼時&#xff0c;我們會常常使用“繼承”這種開發方式。例如下面這一段代碼&#xff1a;class Info:def __init__(self):passdef calc_age(self):print(我是父類的方法) class PeopleInfo(Info):def __init__(self):super().__init__()def calc_ag…

云服務器 自有操作系統,云服務器 自有操作系統

云服務器 自有操作系統 內容精選換一換監控是保持云耀云服務器可靠性、可用性和性能的重要部分&#xff0c;通過監控&#xff0c;用戶可以觀察云耀云服務器資源。為使用戶更好地掌握自己的云耀云服務器運行狀態&#xff0c;公有云平臺提供了云監控。您可以使用該服務監控您的云…

分割線不顯示_90后都30歲了,為什么還不結婚

2020年中國第一批90后已經30歲了。在傳統觀念里&#xff0c;30歲作為人生的分水嶺&#xff0c;成家&#xff0c;立業&#xff0c;結婚&#xff0c;生子&#xff0c;通通要在這之前解決掉&#xff0c;才算趕上了&#xff0c;人生的進度條&#xff0c;然而媒體針對90后&#xff0…

點到線段的距離_直線垂直,垂線的性質,點到直線的距離

歡迎關注公z號&#xff1a;沈陽奧數兩條直線相交所成的四個角中&#xff0c;有一個角是直角時&#xff0c;就說這兩條直線互相垂直&#xff0c;其中一條直線叫做另一條直線的垂線&#xff0c;它們的交點叫垂足。如圖&#xff0c;直線AB與CD垂直于點E&#xff0c;記作&#xff1…

rasa算法_(十八)基于RASA開始中文機器人實現機制

前文介紹了基于RASA的總體架構&#xff0c;本文著重介紹一下實現細節。機器人管理概述框架是多租戶SAAS系統&#xff0c;每個用戶可以創建多個機器人&#xff0c;每個機器人關聯獨立的語料庫&#xff0c;機器人能力&#xff0c;話術流程&#xff0c;在RASA中對應一個RASA運行實…

distinct返回null報錯_C#之集合常用擴展方法與Linq

一、集合的常用擴展方法(lambda的方式)1.Where() 根據條件選擇數據2.Select() 根據數據條件轉換成新的數據類型&#xff0c;類似于DTO轉換類3.Max() 根據條件選擇最大值4.Min() 根據條件選擇最小值5.OrderBy() 根據條件升序排序如果升序中Id都為1&#xff0c;那么就根據第二個條…

python tts 保存_Python 文件和目錄操作學習

文件與文件路徑文件有兩個關鍵屬性&#xff1a;文件名和路徑。路徑指明了文件在計算機上的位置。文件名中&#xff0c;最后一個句點之后的部分稱為文件的“擴展名”&#xff0c;它指出了文件的類型目錄也叫文件夾&#xff0c;文件夾可以包含文件和其他文件夾路徑分隔符在 Windo…

圖片 過度曝光_解讀:攝影初學者,如何理性處理“曝光不足”與“曝光過度”...

曝光是攝影的基本要素之一&#xff0c;但是許多攝影初學者在曝光不足和過度曝光的問題上經常會遇到很多的困擾&#xff0c;甚至完全不知道如何處理這些問題。其實知道如何獲得正確的曝光&#xff0c;并不是你了解曝光過度和曝光不足照片區別的唯一原因。因為創造性的表達比技術…

win7電腦誤刪鼠標鍵盤驅動_鼠標鍵盤,教您怎么解決鍵盤和鼠標失靈的問題

有的時候在我們使用電腦的過程中會突然間有鍵盤鼠標失靈的情況發生&#xff0c;而我們都是不明所以、不知所措的。對此&#xff0c;小編我給你們找了解決方法。接下來&#xff0c;就讓我們一起往下看看關于鍵盤鼠標失靈的解決方法吧。鍵盤和鼠標都是電腦的重要組成部分&#xf…

airpods刪除別人的配對_怎么不讓別人連我的airpods

airpods很容易就被朋友拿混了&#xff0c;到時候分不清自己的airpods耳機是一件很尷尬的事情。那么&#xff0c;airpods如何避免和別人混拿&#xff1f;不拿出來是最好的解決辦法&#xff0c;也可以提前設置不讓別人連我的airpods&#xff0c;這樣是最靠譜的方法。怎么不讓別人…

jmeter安裝包雙擊沒反應_windows環境下Jmeter5.2的安裝使用

標簽&#xff1a;target 首頁 環境變量 百度搜索 bsp nbsp htm targe oracl一、安裝配置JDKJmeter5.2依賴JDK1.8 版本&#xff0c;JDK安裝百度搜索JAVA下載JDK&#xff0c;地址&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/index.ht…

php把中文寫入mysql_php寫入mysql中文亂碼的實例解決方法

php寫入mysql出現中文亂碼的解決辦法是&#xff1a;在建立數據庫連接之后&#xff0c;將該連接的編碼方式改為中文。代碼如下&#xff1a;$linkIDmysql_connect("localhost","root","admin");if(!$linkID){echo "數據庫連接失敗&#xff01…