ConcurrentHashMap 解讀

?


?

初始化:

問題:如何當且僅只有一個線程初始化table

 1 private final Node<K,V>[] initTable() {
 2         Node<K,V>[] tab; int sc;
 3         while ((tab = table) == null || tab.length == 0) {
 4             if ((sc = sizeCtl) < 0)
 5                 Thread.yield(); // lost initialization race; just spin
 6             else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
 7                 try {
 8                     if ((tab = table) == null || tab.length == 0) {
 9                         int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
10                         @SuppressWarnings("unchecked")
11                         Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
12                         table = tab = nt;
13                         sc = n - (n >>> 2);
14                     }
15                 } finally {
16                     sizeCtl = sc;
17                 }
18                 break;
19             }
20         }
21         return tab;
22     }
    transient volatile Node<K,V>[] table;private transient volatile int sizeCtl;

1、第3行? 判斷當前系統的table是否為空,這里用volatile 修飾table,對于各個線程都是可見的

2、第4行 判斷sizeCtl 是否小于零,因為在初始化的過程中,會把sizeCtl設置成-1,所以如果小于零,說明當前有其他線程正在進行初始化,所以直接讓出cpu時間

3、第6行 如果上一步的判斷不小于零,那么這一步就要把sizeCtl設置成-1,這里用Unsafe類保證了僅只有一個線程能修改sizeCtl的值從0到-1

4、9-13行 初始化table

5、第16行 將sizeCtl 值設置為12

?


?

取值

 1 public V get(Object key) {
 2         Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
 3         int h = spread(key.hashCode());
 4         if ((tab = table) != null && (n = tab.length) > 0 &&
 5             (e = tabAt(tab, (n - 1) & h)) != null) {
 6             if ((eh = e.hash) == h) {
 7                 if ((ek = e.key) == key || (ek != null && key.equals(ek)))
 8                     return e.val;
 9             }
10             else if (eh < 0)
11                 return (p = e.find(h, key)) != null ? p.val : null;
12             while ((e = e.next) != null) {
13                 if (e.hash == h &&
14                     ((ek = e.key) == key || (ek != null && key.equals(ek))))
15                     return e.val;
16             }
17         }
18         return null;
19     }
static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}

?

1.? 第2行 定義一堆變量下面用

2. 第3行調整hash值分布的方法,類似于HashMap中的hash(Object key)方法,通過位運算來使key分布更均勻

3. 第4-5行判斷 table 是否有值,如果有值 根據第三行計算出來的結果 計算出key在tab中的Node位置,取得Node中的first node

4. 第6-9行 hash值比對判斷 如果剛剛取得的first node的hash值和當前key的hash值相同,那么就開始獲取值

5. 第10-11 如果剛剛取的node的hash值小于0,那么這個node是個紅黑樹,進入到紅黑樹里面查詢

6.第12-17 如果以上條件都不對,那么就開始遍歷查詢

?


?

擴容:

1.擴容的時機

  1.鏈表新增節點后,所在鏈表的節點數會達到閾值,轉變成紅黑樹,在轉變之前會對數組長度做一次判斷,如果數組的長度小于64,則會用調用tryPresize方法把數組長度擴大到原來的兩倍,并觸發transfer方法,重新調整節點的位置

static final int MIN_TREEIFY_CAPACITY = 64;

?

  2.新增節點之后,會調用addCount方法記錄元素個數,并檢查是否需要進行擴容,當數組元素個數達到閾值時,會觸發transfer方法,重新調整節點的位置

2.擴容的執行:

  1 /**
  2     * 一個過渡的table表  只有在擴容的時候才會使用
  3     */
  4    private transient volatile Node<K,V>[] nextTable;
  5  
  6 /**
  7     * Moves and/or copies the nodes in each bin to new table. See
  8     * above for explanation.
  9     */
 10    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
 11        int n = tab.length, stride;
 12        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
 13            stride = MIN_TRANSFER_STRIDE; // subdivide range
 14        if (nextTab == null) {            // initiating
 15            try {
 16                @SuppressWarnings("unchecked")
 17                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//構造一個nextTable對象 它的容量是原來的兩倍
 18                nextTab = nt;
 19            } catch (Throwable ex) {      // try to cope with OOME
 20                sizeCtl = Integer.MAX_VALUE;
 21                return;
 22            }
 23            nextTable = nextTab;
 24            transferIndex = n;
 25        }
 26        int nextn = nextTab.length;
 27        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//構造一個連節點指針 用于標志位
 28        boolean advance = true;//并發擴容的關鍵屬性 如果等于true 說明這個節點已經處理過
 29        boolean finishing = false; // to ensure sweep before committing nextTab
 30        for (int i = 0, bound = 0;;) {
 31            Node<K,V> f; int fh;
 32            //這個while循環體的作用就是在控制i--  通過i--可以依次遍歷原hash表中的節點
 33            while (advance) {
 34                int nextIndex, nextBound;
 35                if (--i >= bound || finishing)
 36                    advance = false;
 37                else if ((nextIndex = transferIndex) <= 0) {
 38                    i = -1;
 39                    advance = false;
 40                }
 41                else if (U.compareAndSwapInt
 42                         (this, TRANSFERINDEX, nextIndex,
 43                          nextBound = (nextIndex > stride ?
 44                                       nextIndex - stride : 0))) {
 45                    bound = nextBound;
 46                    i = nextIndex - 1;
 47                    advance = false;
 48                }
 49            }
 50            if (i < 0 || i >= n || i + n >= nextn) {
 51                int sc;
 52                if (finishing) {
 53                    //如果所有的節點都已經完成復制工作  就把nextTable賦值給table 清空臨時對象nextTable
 54                    nextTable = null;
 55                    table = nextTab;
 56                    sizeCtl = (n << 1) - (n >>> 1);//擴容閾值設置為原來容量的1.5倍  依然相當于現在容量的0.75倍
 57                    return;
 58                }
 59                //利用CAS方法更新這個擴容閾值,在這里面sizectl值減一,說明新加入一個線程參與到擴容操作
 60                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
 61                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
 62                        return;
 63                    finishing = advance = true;
 64                    i = n; // recheck before commit
 65                }
 66            }
 67            //如果遍歷到的節點為空 則放入ForwardingNode指針
 68            else if ((f = tabAt(tab, i)) == null)
 69                advance = casTabAt(tab, i, null, fwd);
 70            //如果遍歷到ForwardingNode節點  說明這個點已經被處理過了 直接跳過  這里是控制并發擴容的核心
 71            else if ((fh = f.hash) == MOVED)
 72                advance = true; // already processed
 73            else {
 74                    //節點上鎖
 75                synchronized (f) {
 76                    if (tabAt(tab, i) == f) {
 77                        Node<K,V> ln, hn;
 78                        //如果fh>=0 證明這是一個Node節點
 79                        if (fh >= 0) {
 80                            int runBit = fh & n;
 81                            //以下的部分在完成的工作是構造兩個鏈表  一個是原鏈表  另一個是原鏈表的反序排列
 82                            Node<K,V> lastRun = f;
 83                            for (Node<K,V> p = f.next; p != null; p = p.next) {
 84                                int b = p.hash & n;
 85                                if (b != runBit) {
 86                                    runBit = b;
 87                                    lastRun = p;
 88                                }
 89                            }
 90                            if (runBit == 0) {
 91                                ln = lastRun;
 92                                hn = null;
 93                            }
 94                            else {
 95                                hn = lastRun;
 96                                ln = null;
 97                            }
 98                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
 99                                int ph = p.hash; K pk = p.key; V pv = p.val;
100                                if ((ph & n) == 0)
101                                    ln = new Node<K,V>(ph, pk, pv, ln);
102                                else
103                                    hn = new Node<K,V>(ph, pk, pv, hn);
104                            }
105                            //在nextTable的i位置上插入一個鏈表
106                            setTabAt(nextTab, i, ln);
107                            //在nextTable的i+n的位置上插入另一個鏈表
108                            setTabAt(nextTab, i + n, hn);
109                            //在table的i位置上插入forwardNode節點  表示已經處理過該節點
110                            setTabAt(tab, i, fwd);
111                            //設置advance為true 返回到上面的while循環中 就可以執行i--操作
112                            advance = true;
113                        }
114                        //對TreeBin對象進行處理  與上面的過程類似
115                        else if (f instanceof TreeBin) {
116                            TreeBin<K,V> t = (TreeBin<K,V>)f;
117                            TreeNode<K,V> lo = null, loTail = null;
118                            TreeNode<K,V> hi = null, hiTail = null;
119                            int lc = 0, hc = 0;
120                            //構造正序和反序兩個鏈表
121                            for (Node<K,V> e = t.first; e != null; e = e.next) {
122                                int h = e.hash;
123                                TreeNode<K,V> p = new TreeNode<K,V>
124                                    (h, e.key, e.val, null, null);
125                                if ((h & n) == 0) {
126                                    if ((p.prev = loTail) == null)
127                                        lo = p;
128                                    else
129                                        loTail.next = p;
130                                    loTail = p;
131                                    ++lc;
132                                }
133                                else {
134                                    if ((p.prev = hiTail) == null)
135                                        hi = p;
136                                    else
137                                        hiTail.next = p;
138                                    hiTail = p;
139                                    ++hc;
140                                }
141                            }
142                            //如果擴容后已經不再需要tree的結構 反向轉換為鏈表結構
143                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
144                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
145                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
146                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
147                             //在nextTable的i位置上插入一個鏈表    
148                            setTabAt(nextTab, i, ln);
149                            //在nextTable的i+n的位置上插入另一個鏈表
150                            setTabAt(nextTab, i + n, hn);
151                             //在table的i位置上插入forwardNode節點  表示已經處理過該節點
152                            setTabAt(tab, i, fwd);
153                            //設置advance為true 返回到上面的while循環中 就可以執行i--操作
154                            advance = true;
155                        }
156                    }
157                }
158            }
159        }
160    }

?


?插入:

 1 final V putVal(K key, V value, boolean onlyIfAbsent) {
 2         //key和value都不能為空
 3         if (key == null || value == null) throw new NullPointerException();
 4         //計算hash值,讓hash值分布更均勻
 5         int hash = spread(key.hashCode());
 6         int binCount = 0;
 7         //什么時候插入成功,什么時候跳出
 8         for (Node<K,V>[] tab = table;;) {
 9             Node<K,V> f; int n, i, fh;
10             //如果table為空的話,進行初始化操作
11             if (tab == null || (n = tab.length) == 0)
12                 tab = initTable();
13             //根據hash值計算出在table里面的位置,返回table[i]這個節點信息,賦值給f 
14             else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
15                 //如果這個位置沒有值 ,直接放進去,不需要加鎖
16                 if (casTabAt(tab, i, null,
17                              new Node<K,V>(hash, key, value, null)))
18                     break;                   // no lock when adding to empty bin
19             }
20             //如果當前的這個節點正在擴容,那就幫助擴容線程進行擴容
21             else if ((fh = f.hash) == MOVED)
22                 tab = helpTransfer(tab, f);
23             else {
24                 V oldVal = null;
25                 //對節點加鎖
26                 synchronized (f) {
27                     //再次判斷,多線程下有可能會出問題
28                     if (tabAt(tab, i) == f) {
29                         //fh〉0 說明這個節點是一個鏈表的節點 不是樹的節點
30                         if (fh >= 0) {
31                             binCount = 1;
32                             //在這里遍歷鏈表所有的結點
33                             for (Node<K,V> e = f;; ++binCount) {
34                                 K ek;
35                                 //如果hash值和key值相同  則修改對應結點的value值
36                                 if (e.hash == hash &&
37                                     ((ek = e.key) == key ||
38                                      (ek != null && key.equals(ek)))) {
39                                     oldVal = e.val;
40                                     if (!onlyIfAbsent)
41                                         e.val = value;
42                                     break;
43                                 }
44                                 Node<K,V> pred = e;
45                                 //如果遍歷到了最后一個結點,那么就證明新的節點需要插入 就把它插入在鏈表尾部
46                                 if ((e = e.next) == null) {
47                                     pred.next = new Node<K,V>(hash, key,
48                                                               value, null);
49                                     break;
50                                 }
51                             }
52                         }
53                         //如果這個節點是樹節點,就按照樹的方式插入值
54                         else if (f instanceof TreeBin) {
55                             Node<K,V> p;
56                             binCount = 2;
57                             if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
58                                                            value)) != null) {
59                                 oldVal = p.val;
60                                 if (!onlyIfAbsent)
61                                     p.val = value;
62                             }
63                         }
64                     }
65                 }
66                 if (binCount != 0) {
67                     //如果鏈表長度已經達到臨界值8 就需要把鏈表轉換為樹結構
68                     if (binCount >= TREEIFY_THRESHOLD)
69                         treeifyBin(tab, i);
70                     if (oldVal != null)
71                         return oldVal;
72                     break;
73                 }
74             }
75         }
76          //將當前ConcurrentHashMap的元素數量+1
77         addCount(1L, binCount);
78         return null;
79     }

?

?


?

參考:

https://www.jianshu.com/p/f6730d5784ad

http://www.importnew.com/22007.html

轉載于:https://www.cnblogs.com/xmzJava/p/8353032.html

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

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

相關文章

XML Schema 基本結構

<?xml version1.0?> <Schema name"cangchuSchema" metamodelVersion"4.0"><PhysicalSchema><Table name"highway_toll"><Key><Column name"uid"/></Key></Table><Table name&qu…

關于系統自帶 .NET Framework 版本的說明

系統自帶版本&#xff1a; Windows XP (SP3) .NET Framework 1.1Windows 7 (SP1) .NET Framework 3.5.1Windows 8.1 .NET Framework 4.5.1Windows 10 (1507) .NET Framework 4.6Windows 10 (1511) .NET Framework 4.6.1Windows 10 (1607) .NET Framework 4.6.2Windows 10 (1703…

BundleFusion那些事兒

背景&#xff1a;前面幾篇博客中寫了很多關于BundleFusion的東西&#xff0c;主要包括bundlefusion的論文閱讀筆記&#xff0c;.sens數據集的生成等&#xff0c;經過最近幾天的工作&#xff0c;我對bundlefusion又有了新的技術積累&#xff0c;在這里整理一下&#xff0c;也算是…

問題:圖片怎么保存到數據庫, 以及怎么把圖片從數據庫中取出來使用?(已解決)...

簡單&#xff0c;不保存圖片到數據庫&#xff0c;而是圖片的路徑。 也就是說&#xff0c;先把圖片下載到服務器的指定目錄下&#xff0c;然后&#xff0c;在把相對的路徑保存到數據庫中。 如果下次獲取圖片&#xff0c;就訪問數據庫&#xff0c;獲取圖片路徑&#xff0c;然后根…

Oracle Study之--Oracle 11gR2通過RMAN克隆數據庫

Oracle Study之--Oracle 11gR2通過RMAN克隆數據庫Purpose of Database Duplication A duplicate database is useful for a variety of purposes, most of which involve testing. You can perform the following tasks in a duplicate database: Test backup and recovery pro…

ubuntu16.04安裝evo

背景:這已經是我第二次嘗試安裝evo了,最近因為在bundlefusion加入groundtruth的問題搞的很煩躁,我懷疑是不是我給定的groundtruth是不是不正確,所以我就寫python腳本,獲取計算生成的位姿數據,寫入的groundtruth位姿數據.獲取數據后,將數據可視化就成了一個很重要的問題,我還是打…

前端性能優化之gzip

gzip是GNUzip的縮寫&#xff0c;它是一個GNU自由軟件的文件壓縮程序。它最早由Jean-loup Gailly和Mark Adler創建&#xff0c;用于UNⅨ系統的文件壓縮。我們在Linux中經常會用到后綴為.gz的文件&#xff0c;它們就是GZIP格式的。現今已經成為Internet 上使用非常普遍的一種數據…

luogu2770 航空路線問題 網絡流

題目大意&#xff1a; 給定一張航空圖&#xff0c;圖中頂點代表城市&#xff0c;邊代表 2 城市間的直通航線。現要求找出一條滿足下述限制條件的且途經城市最多的旅行路線。(1)從最西端城市出發&#xff0c;單向從西向東途經若干城市到達最東端城市&#xff0c;然后再單向從東向…

手機錄音ogg格式怎么轉換mp3

Ogg這種音頻格式剛出來的時候大家是非常熱愛的&#xff0c;但是隨著時代的發展&#xff0c;這種音頻格式已經已經被取代了&#xff0c;現在呢走在音頻格式前端的是MP3格式&#xff0c;這是大家都比較熟悉的&#xff0c;但是我們還是會經常下載到ogg這種格式的音頻&#xff0c;就…

TP3.2設置URL偽靜態滿足更好的SEO效果

URL偽靜態通常是為了滿足更好的SEO效果&#xff0c;ThinkPHP支持偽靜態URL設置&#xff0c;可以通過設置URL_HTML_SUFFIX參數隨意在URL的最后增加你想要的靜態后綴&#xff0c;而不會影響當前操作的正常執行。 例如&#xff0c;我們設置 URL_HTML_SUFFIX>shtml 的話&#xf…

[機器學習] 推薦系統之協同過濾算法(轉)

[機器學習]推薦系統之協同過濾算法 在現今的推薦技術和算法中&#xff0c;最被大家廣泛認可和采用的就是基于協同過濾的推薦方法。本文將帶你深入了解協同過濾的秘密。下面直接進入正題. 1. 什么是推薦算法 推薦算法最早在1992年就提出來了&#xff0c;但是火起來實際上是最近這…

BundleFusion代碼框架講解

背景&#xff1a;前面用了幾篇文章來記錄和總結了&#xff0c;我在研究bundlefusion過程中遇到的一些問題以及解決方法&#xff0c;本來想實現給bundlefusion輸入先驗軌跡&#xff0c;然后讓其根據給定的軌跡進行重建&#xff0c;這樣即便在環境比較惡劣的情況下&#xff0c;也…

BundlePhobia

1、BundlePhobia用于分析npm package的依賴、bundle后的大小、下載速度預估等等&#xff0c;幫助你在引用一個package之前了解引入該package的代價。 2、也可以將項目的package.json文件上傳&#xff0c;BundlePhobia會幫你評估項目中所有包的大小和加載速度。

VFL演示樣例

VFL演示樣例 上篇文章向大家介紹了VFL的基本的語法點&#xff0c;假設對下面演示樣例不熟的童鞋&#xff0c;能夠前去參考。廢話不多說。我們直接來看演示樣例。演示樣例一 將五個大小同樣、顏色不同的view排成一行&#xff0c;view間的間隔為15px,第一個view的間隔與屏幕的左邊…

evo實用指令指南

下面這篇文章中有比較具體的關于evo的安裝以及使用的介紹&#xff0c;其中一點很重要&#xff0c;就是可以把euroc形式的.csv的軌跡格式轉換為tum格式的軌跡。 https://zhuanlan.zhihu.com/p/88223106#single evo_traj tum orb_slam2_tum.txt --reftum_groundtruth.txt -p --pl…

MailUtils

/***包名:com.thinkgem.jeesite.test*描述:package com.thinkgem.jeesite.test;*/ package com.thinkgem.jeesite.test;import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.u…

ES6遍歷對象

遍歷對象 E S 6 一共有 5 種方法可以遍歷對象的屬性 。 for ... in for . . . in 循環遍歷對象自身的和繼承的可枚舉屬性&#xff08;不含 Symbol 屬性&#xff09;。 Object.keys(obj)Object . keys 返回 一個數組&#xff0c;包括對象自身的&#xff08;不含繼承的 &#xff…

SpringMvc中ModelAndView模型的應用

/** * 目標方法的返回值可以是 ModelAndView 類型。 * 其中可以包含視圖和模型信息 * SpringMVC 會把 ModelAndView 的 model 中數據放入到 request 域對象中. * return */ RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ String v…

ubuntu16.04 + ros-kinetic 配置cartographer

其實一直以來都感覺純視覺SLAM很難落地產品&#xff0c;所以一直在找機會學習激光slam,之前也在深藍學院上買了一個激光salm的課程&#xff0c;慚愧&#xff0c;至今也沒開始學呢&#xff0c;年底之前&#xff0c;我想工作之余研究一下激光slam和ros&#xff0c;我感覺這兩個東…

virtualbox中安裝ubuntu

為什么80%的碼農都做不了架構師&#xff1f;>>> virtualboxubuntu 安裝virtualbox&#xff0c;當前版本是6.0.4下載ubuntu安裝盤&#xff0c;建議lubuntu&#xff0c;鏈接是http://mirrors.ustc.edu.cn/ubuntu-cdimage/lubuntu/releases/18.04.2/release/lubuntu-1…