摘要:數(shù)據(jù)結(jié)構(gòu)重要成員變量代表整個(gè)哈希表??破?,解決多線程并行情況下使用鎖造成性能損耗的一種機(jī)制,操作包含三個(gè)操作數(shù)內(nèi)存位置預(yù)期原值和新值。
ConcurrenHashMap 。下面分享一下我對(duì)ConcurrentHashMap 的理解,主要用于個(gè)人備忘。如果有不對(duì),請(qǐng)批評(píng)。
HashMap“嚴(yán)重”的勾起了我對(duì)HashMap家族的好奇心,下面分享一下我對(duì)ConcurrentHashMap 的理解,主要用于個(gè)人備忘。如果有不對(duì),請(qǐng)批評(píng)。
想要解鎖更多新姿勢(shì)?請(qǐng)?jiān)L問https://blog.tengshe789.tech/
總起HashMap是我們平時(shí)開發(fā)過程中用的比較多的集合,但它是非線程安全的,在涉及到多線程并發(fā)的情況,進(jìn)行g(shù)et操作有可能會(huì)引起死循環(huán),導(dǎo)致CPU利用率接近100%。
因此需要支持線程安全的并發(fā)容器 ConcurrentHashMap 。
數(shù)據(jù)結(jié)構(gòu) 重要成員變量/** * The array of bins. Lazily initialized upon first insertion. * Size is always a power of two. Accessed directly by iterators. */ transient volatile Node[] table;
table代表整個(gè)哈希表。 默認(rèn)為null,初始化發(fā)生在第一次插入操作,默認(rèn)大小為16的數(shù)組,用來存儲(chǔ)Node節(jié)點(diǎn)數(shù)據(jù),擴(kuò)容時(shí)大小總是2的冪次方。
/** * The next table to use; non-null only while resizing. */ private transient volatile Node[] nextTable;
nextTable是一個(gè)連接表,用于哈希表擴(kuò)容,默認(rèn)為null,擴(kuò)容時(shí)新生成的數(shù)組,其大小為原數(shù)組的兩倍。
/** * Base counter value, used mainly when there is no contention, * but also as a fallback during table initialization * races. Updated via CAS. */ private transient volatile long baseCount;
baseCount保存著整個(gè)哈希表中存儲(chǔ)的所有的結(jié)點(diǎn)的個(gè)數(shù)總和,有點(diǎn)類似于 HashMap 的 size 屬性。 這個(gè)數(shù)通過CAS算法更新
/** * Table initialization and resizing control. When negative, the * table is being initialized or resized: -1 for initialization, * else -(1 + the number of active resizing threads). Otherwise, * when table is null, holds the initial table size to use upon * creation, or 0 for default. After initialization, holds the * next element count value upon which to resize the table. */ private transient volatile int sizeCtl;
初始化哈希表和擴(kuò)容 rehash 的過程,都需要依賴sizeCtl。該屬性有以下幾種取值:
0:默認(rèn)值
-1:代表哈希表正在進(jìn)行初始化
大于0:相當(dāng)于 HashMap 中的 threshold,表示閾值
小于-1:代表有多個(gè)線程正在進(jìn)行擴(kuò)容。(譬如:-N 表示有N-1個(gè)線程正在進(jìn)行擴(kuò)容操作 )
構(gòu)造方法public ConcurrentHashMap() { } public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));//MAXIMUM_CAPACITY = 1 << 30 this.sizeCtl = cap;//ConcurrentHashMap在構(gòu)造函數(shù)中只會(huì)初始化sizeCtl值,并不會(huì)直接初始化table,而是延緩到第一次put操作。 } public ConcurrentHashMap(Map extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY;//DEFAULT_CAPACITY = 16 putAll(m); }
構(gòu)造方法是三個(gè)。重點(diǎn)是第二個(gè),帶參的構(gòu)造方法。這個(gè)帶參的構(gòu)造方法會(huì)調(diào)用tableSizeFor()方法,確保table的大小總是2的冪次方(假設(shè)參數(shù)為100,最終會(huì)調(diào)整成256)。算法如下:
/** * Returns a power of two table size for the given desired capacity. * See Hackers Delight, sec 3.2 */ private static final int tableSizeFor(int c) { int n = c - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }PUT()方法
put()調(diào)用putVal()方法,讓我們看看:
final V putVal(K key, V value, boolean onlyIfAbsent) { //對(duì)傳入的參數(shù)進(jìn)行合法性判斷 if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode());//計(jì)算鍵所對(duì)應(yīng)的 hash 值 int binCount = 0; for (Node[] tab = table;;) { Node f; int n, i, fh; //如果哈希表還未初始化,那么初始化它 if (tab == null || (n = tab.length) == 0) tab = initTable(); //根據(jù)hash值計(jì)算出在table里面的位置 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //如果這個(gè)位置沒有值 ,那么以CAS無鎖式向該位置添加一個(gè)節(jié)點(diǎn) if (casTabAt(tab, i, null, new Node (hash, key, value, null))) break; // no lock when adding to empty bin } //檢測到桶結(jié)點(diǎn)是 ForwardingNode 類型,協(xié)助擴(kuò)容(MOVED = -1; // hash for forwarding nodes) else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); //桶結(jié)點(diǎn)是普通的結(jié)點(diǎn),鎖住該桶頭結(jié)點(diǎn)并試圖在該鏈表的尾部添加一個(gè)節(jié)點(diǎn) else { V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { //向普通的鏈表中添加元素 if (fh >= 0) { binCount = 1; //遍歷鏈表所有的結(jié)點(diǎn) for (Node e = f;; ++binCount) { K ek; //如果hash值和key值相同,則修改對(duì)應(yīng)結(jié)點(diǎn)的value值 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node pred = e; //如果遍歷到了最后一個(gè)結(jié)點(diǎn),那么就證明新的節(jié)點(diǎn)需要插入鏈表尾部 if ((e = e.next) == null) { pred.next = new Node (hash, key, value, null); break; } } } //如果這個(gè)節(jié)點(diǎn)是樹節(jié)點(diǎn),就按照樹的方式插入值 else if (f instanceof TreeBin) { Node p; binCount = 2; if ((p = ((TreeBin )f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { //如果鏈表長度已經(jīng)達(dá)到臨界值8,就需要把鏈表轉(zhuǎn)換為樹結(jié)構(gòu)(TREEIFY_THRESHOLD = 8) if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } //CAS 式更新baseCount,并判斷是否需要擴(kuò)容 addCount(1L, binCount); return null; }
其實(shí)putVal()也多多少少掉用了其他方法,讓我們繼續(xù)探究一下。
CAS(compare and swap)科普compare and swap,解決多線程并行情況下使用鎖造成性能損耗的一種機(jī)制,CAS操作包含三個(gè)操作數(shù)——內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會(huì)在CAS指令之前返回該位置的值。CAS有效地說明了“我認(rèn)為位置V應(yīng)該包含值A(chǔ);如果包含該值,則將B放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。
spread首先,第四行出現(xiàn)的int hash = spread(key.hashCode());這是傳統(tǒng)的計(jì)算hash的方法。key的hash值高16位不變,低16位與高16位異或作為key的最終hash值。(h >>> 16,表示無符號(hào)右移16位,高位補(bǔ)0,任何數(shù)跟0異或都是其本身,因此key的hash值高16位不變。)
static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }initTable
第十行, tab = initTable();這個(gè)方法的亮點(diǎn)是,可以讓put并發(fā)執(zhí)行,實(shí)現(xiàn)table只初始化一次 。
initTable()核心思想就是,只允許一個(gè)線程對(duì)表進(jìn)行初始化,如果有其他線程進(jìn)來了,那么會(huì)讓其他線程交出 CPU 等待下次系統(tǒng)調(diào)度。這樣,保證了表同時(shí)只會(huì)被一個(gè)線程初始化。
private final Node[] initTable() { Node [] tab; int sc; //如果表為空才進(jìn)行初始化操作 while ((tab = table) == null || tab.length == 0) { //如果一個(gè)線程發(fā)現(xiàn)sizeCtl<0,意味著另外的線程執(zhí)行CAS操作成功,當(dāng)前線程只需要讓出cpu時(shí)間片(放棄 CPU 的使用) if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //否則說明還未有線程對(duì)表進(jìn)行初始化,那么本線程就來做這個(gè)工作 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { //sc 大于零說明容量已經(jīng)初始化了,否則使用默認(rèn)容量 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node [] nt = (Node [])new Node,?>[n]; table = tab = nt; //計(jì)算閾值,等效于 n*0.75 sc = n - (n >>> 2); } } finally { //設(shè)置閾值 sizeCtl = sc; } break; } } return tab; }
接下來,第19行。 tab = helpTransfer(tab, f);這句話。要了解這個(gè),首先需要知道ForwardingNode 這個(gè)節(jié)點(diǎn)類型。它一個(gè)用于連接兩個(gè)table的節(jié)點(diǎn)類。它包含一個(gè)nextTable指針,用于指向下一張hash表。而且這個(gè)節(jié)點(diǎn)的key、value、next指針全部為null,它的hash值為MOVED(static final int MOVED = -1)。
static final class ForwardingNodehelpTransferextends Node { final Node [] nextTable; ForwardingNode(Node [] tab) { super(MOVED, null, null, null); this.nextTable = tab; } //find的方法是從nextTable里進(jìn)行查詢節(jié)點(diǎn),而不是以自身為頭節(jié)點(diǎn)進(jìn)行查找 Node find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node [] tab = nextTable;;) { Node e; int n; if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; for (;;) { int eh; K ek; if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; if (eh < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode )e).nextTable; continue outer; } else return e.find(h, k); } if ((e = e.next) == null) return null; } } } }
在擴(kuò)容操作中,我們需要對(duì)每個(gè)桶中的結(jié)點(diǎn)進(jìn)行分離和轉(zhuǎn)移。如果某個(gè)桶結(jié)點(diǎn)中所有節(jié)點(diǎn)都已經(jīng)遷移完成了(已經(jīng)被轉(zhuǎn)移到新表 nextTable 中了),那么會(huì)在原 table 表的該位置掛上一個(gè) ForwardingNode 結(jié)點(diǎn),說明此桶已經(jīng)完成遷移。
helpTransfer什么作用呢?是檢測到當(dāng)前哈希表正在擴(kuò)容,然后讓當(dāng)前線程去協(xié)助擴(kuò)容 !
final Node[] helpTransfer(Node [] tab, Node f) { Node [] nextTab; int sc; if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode )f).nextTable) != null) {//新的table,nextTab已經(jīng)存在前提下才能幫助擴(kuò)容 int rs = resizeStamp(tab.length);//返回一個(gè) 16 位長度的擴(kuò)容校驗(yàn)標(biāo)識(shí) while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {//sizeCtl 如果處于擴(kuò)容狀態(tài)的話 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) //前 16 位是數(shù)據(jù)校驗(yàn)標(biāo)識(shí),后 16 位是當(dāng)前正在擴(kuò)容的線程總數(shù) //這里判斷校驗(yàn)標(biāo)識(shí)是否相等,如果校驗(yàn)符不等或者擴(kuò)容操作已經(jīng)完成了,直接退出循環(huán),不用協(xié)助它們擴(kuò)容了 break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {//sc + 1 標(biāo)識(shí)增加了一個(gè)線程進(jìn)行擴(kuò)容 transfer(tab, nextTab);//調(diào)用擴(kuò)容方法 break; } } return nextTab; } return table; }
helpTransfer精髓的是可以調(diào)用多個(gè)工作線程一起幫助進(jìn)行擴(kuò)容,這樣的效率就會(huì)更高,而不是只有檢查到要擴(kuò)容的那個(gè)線程進(jìn)行擴(kuò)容操作,其他線程就要等待擴(kuò)容操作完成才能工作。
transfer既然這里涉及到擴(kuò)容的操作,我們也一起來看看擴(kuò)容方法transfer() :
private final void transfer(Node[] tab, Node [] nextTab) { int n = tab.length, stride; //計(jì)算單個(gè)線程允許處理的最少table桶首節(jié)點(diǎn)個(gè)數(shù),不能小于 16 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range //剛開始擴(kuò)容,初始化 nextTab if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") Node [] nt = (Node [])new Node,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; //transferIndex 指向最后一個(gè)桶,方便從后向前遍歷 transferIndex = n; } int nextn = nextTab.length; //定義 ForwardingNode 用于標(biāo)記遷移完成的桶 ForwardingNode fwd = new ForwardingNode (nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab //i 指向當(dāng)前桶,bound 指向當(dāng)前線程需要處理的桶結(jié)點(diǎn)的區(qū)間下限 for (int i = 0, bound = 0;;) { Node f; int fh; //遍歷當(dāng)前線程所分配到的桶結(jié)點(diǎn) while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; //transferIndex <= 0 說明已經(jīng)沒有需要遷移的桶了 else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } //更新 transferIndex //為當(dāng)前線程分配任務(wù),處理的桶結(jié)點(diǎn)區(qū)間為(nextBound,nextIndex) else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } //當(dāng)前線程所有任務(wù)完成 if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; i = n; // recheck before commit } } //待遷移桶為空,那么在此位置 CAS 添加 ForwardingNode 結(jié)點(diǎn)標(biāo)識(shí)該桶已經(jīng)被處理過了 else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); //如果掃描到 ForwardingNode,說明此桶已經(jīng)被處理過了,跳過即可 else if ((fh = f.hash) == MOVED) advance = true; // already processed else { synchronized (f) { if (tabAt(tab, i) == f) { Node ln, hn; //鏈表的遷移操作 if (fh >= 0) { int runBit = fh & n; Node lastRun = f; //整個(gè) for 循環(huán)為了找到整個(gè)桶中最后連續(xù)的 fh & n 不變的結(jié)點(diǎn) for (Node p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } for (Node p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node (ph, pk, pv, ln); else hn = new Node (ph, pk, pv, hn); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } //紅黑樹的復(fù)制算法, else if (f instanceof TreeBin) { TreeBin t = (TreeBin )f; TreeNode lo = null, loTail = null; TreeNode hi = null, hiTail = null; int lc = 0, hc = 0; for (Node e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode p = new TreeNode (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin (lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin (hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } } }
至此,put方法講完了
參考資料~參考資料
感謝
結(jié)束
此片完了~
想要了解更多精彩新姿勢(shì)?請(qǐng)?jiān)L問我的個(gè)人博客
本篇為原創(chuàng)內(nèi)容,已經(jīng)于07-06在個(gè)人博客率先發(fā)表,隨后CSDN,segmentfault,juejin同步發(fā)出。如有雷同,
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76697.html
摘要:變量提升變量的聲明寫在可以在使用變量之后函數(shù)提升函數(shù)可以先調(diào)用,后聲明上面先解釋了下我理解的這兩個(gè)概念的定義。參考前端基礎(chǔ)進(jìn)階三變量對(duì)象詳解關(guān)于變量提升的理解 變量提升:變量的聲明寫在可以在使用變量之后;函數(shù)提升:函數(shù)可以先調(diào)用,后聲明; 上面先解釋了下我理解的這兩個(gè)概念的定義。要真正理解它們,最好從變量對(duì)象的角度出發(fā)。引出變量對(duì)象的概念,要先理解執(zhí)行上下文,也就是當(dāng)控制器執(zhí)行到可執(zhí)行...
摘要:網(wǎng)上關(guān)于閉包的解釋有很多,大多都過于概念化,定義很精準(zhǔn)也很難看懂在說什么。。首先貼一道經(jīng)典的閉包題理解閉包之前,我們要明確垃圾回收機(jī)制中關(guān)于引用次數(shù)的判斷,即當(dāng)引用對(duì)象的引用計(jì)數(shù)為的時(shí)候,表明此對(duì)象值可回收。 網(wǎng)上關(guān)于閉包的解釋有很多,大多都過于概念化,定義很精準(zhǔn)也很難看懂在說什么。。首先貼一道經(jīng)典的閉包題:` function a(){ var b=0 return function...
摘要:當(dāng)容量超過容量負(fù)載因子時(shí),進(jìn)行擴(kuò)容操作確定何時(shí)將沖突的鏈表轉(zhuǎn)換成紅黑樹用來確何時(shí)將紅黑樹轉(zhuǎn)換成鏈表當(dāng)鏈表轉(zhuǎn)換成紅黑樹時(shí),需要判斷數(shù)組容量。桶排序核心思想是根據(jù)數(shù)據(jù)規(guī)模劃分,個(gè)相同大小的區(qū)間每個(gè)區(qū)間為一個(gè)桶,桶可理解為容器。 剛剛看到QQ群有人吹Hashmap,一想我啥都不懂,就趕快補(bǔ)了一波。下面分享一下我對(duì)Hashmap的理解,主要用于個(gè)人備忘。如果有不對(duì),請(qǐng)批評(píng)。想要解鎖更多新姿勢(shì)?...
摘要:對(duì)于一棵有效的紅黑樹二叉樹而言我們必須增加如下規(guī)則每個(gè)節(jié)點(diǎn)都只能是紅色或者黑色根節(jié)點(diǎn)是黑色每個(gè)葉節(jié)點(diǎn)節(jié)點(diǎn),空節(jié)點(diǎn)是黑色的。這些約束強(qiáng)制了紅黑樹的關(guān)鍵性質(zhì)從根到葉子的最長的可能路徑不多于最短的可能路徑的兩倍長。 群里的大哥說了,要想懂紅黑樹的應(yīng)用,先要看TreeMap。 想要解鎖更多新姿勢(shì)?請(qǐng)?jiān)L問http://blog.tengshe789.tech/ OK,現(xiàn)在開始: 紅黑樹簡介 紅黑...
摘要:關(guān)于變量的值的類型的總結(jié)。所以此時(shí)指向新的對(duì)象還是指向被添加了屬性的老對(duì)象, 關(guān)于變量的值的類型的總結(jié)。 //1.當(dāng)多個(gè)變量的值是非引用類型var a=1;var b=a; //系統(tǒng)復(fù)制了a的值并賦值給ba++; //a自身的值被改變,而b的值不受影響 a b的值雖相等但互不影響console.log(a)//2console.log(b)//1 ...
閱讀 3440·2021-11-19 09:40
閱讀 1339·2021-10-11 11:07
閱讀 4869·2021-09-22 15:07
閱讀 2902·2021-09-02 15:15
閱讀 1973·2019-08-30 15:55
閱讀 545·2019-08-30 15:43
閱讀 892·2019-08-30 11:13
閱讀 1460·2019-08-29 15:36