【Java自顶向下】试手小公司,面试官问我ConcurrentHashMap,我微微一笑……
文章目錄
- ConcurrentHashMap
- 一、ConcurrentHashMap初始化的剖析
- 1.1 ConcurrentHashMap初始化
- 1.2 理解sizeCtl
- 二、JDK8的添加安全
- 2.1 putVal源碼分析
- 2.2 putVal源碼程序流程圖
- 2.3 putVal閱讀源碼總結
- 2.4 initTable源碼分析
- 2.5 initTable源碼程序流程圖
- 2.6 JDK8中ConcurrentHashMap添加安全總結
- 三、JDK8擴容安全
- 3.1 transfer源碼分析
- 3.2 擴容安全圖解
- 3.3 JDK8中ConcurrentHashMap擴容安全總結
- 四、JDK8多線程擴容效率改進
- 4.1 方案1:putVal源碼分析
- 4.2 方案2:helpTransfer源碼分析
- 4.3 JDK中ConcurrentHashMap擴容效率提高圖解
- 五、集合長度的累計方式
- 5.1 addCount源碼分析
- 5.2 fullAddCount源碼分析
- 六、jdk1.8集合長度獲取
- 6.1 size源碼
- 6.2 sumCount源碼
- 七、JDK7中ConcurrentHashMap
- 八、相關面試題
ConcurrentHashMap
有別于HashMap的線程不安全和HashTable的低效率(稍微看了一下源碼,發現使用了大量的synchronized關鍵字修飾的同步方法),ConcurrentHashMap使用的是cas來保證整個元素插入、刪除、擴容時候的同步安全。充分解決了HashMap和HashTable存在的問題。
下面需要對ConcurrentHashMap的源碼做一些解讀,讓讀者更好的理解ConcurrentHashMap的底層運行邏輯。
一、ConcurrentHashMap初始化的剖析
1.1 ConcurrentHashMap初始化
如果我們在new一個ConcurrentHashMap的時候給定參數,那么put之后,該ConcurrentHashMap的初始容量為大于給定參數的2的冪次方,比如
// 給定參數32,那么在put之后chm的初始容量為64(JDK7是還是32) ConcurrentHashMap chm = new ConcurrentHashMap(32);原因 是ConcurrentHashMap源碼
public ConcurrentHashMap(int initialCapacity) {if (initialCapacity < 0)throw new IllegalArgumentException();//給定參數大于最大容量的1/2容量?若大于,初始容量為最大值2^30int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY ://參數是32的話,傳入tableSizeFor的參數是32+16+1=49tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));this.sizeCtl = cap; }如果并不大于最大容量的1/2,調用以下函數。
這段代碼是指,右移多少位,就把最高位右邊的第x位設置為1;第一次,就把最右邊為1;第二次,就把右邊第2位再設置為1;第3次,就把右邊第3位再設置為1;這樣執行完,原來是110000(48),變成了111111,最后加1,就變成2的整數次方數了(64)。
private static final int tableSizeFor(int c) {// 傳入49int n = c - 1;// n=48n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;// 此時n=63,最后經過兩次判斷后返回值為64return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }1.2 理解sizeCtl
sizeCtl = 0,代表數組未初始化,且數組初始容量為16
sizeCtl > 0,如果數組未初始化,那么其記錄的是數組的初始容量,如果數組已經初始化,那么其記錄的是數組的擴容閾值
sizeCtl = -1,表示數組正在進行初始化
sizeCtl < 0 && sizeCtl != -1,表示數組正在擴容,-(1+n),表示此時有n個線程正在共同完成數組的擴容操作。
二、JDK8的添加安全
首先我們來看ConcurrentHashMap的添加元素過程的源碼
2.1 putVal源碼分析
public V put(K key, V value) {return putVal(key, value, false);} final V putVal(K key, V value, boolean onlyIfAbsent) {// 如果有空值或空鍵,會直接拋出異常if (key == null || value == null) throw new NullPointerException();// 基于key計算hash值,并進行一定的擾動(目的是使結果分步平均)// 這個值一定是一個整數,方便后面添加元素,判斷該節點的類型int hash = spread(key.hashCode());//記錄某個桶上元素的個數,如果超過8個,會轉成紅黑樹int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;//如果數組還未初始化,先對數組進行初始化if (tab == null || (n = tab.length) == 0)// 解讀源碼1,數組初始化tab = initTable();// if判斷是指,hash函數計算得到的數組下標對應的桶中若為空,就利用cas直接把元素放入數組else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 在這里也使用了cas自旋鎖操作。因為有可能有兩個線程進入當前位置,確保只能有一個線程訪問臨界資源if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin}// 如果hash計算得到的桶位置元素的hash值為MOVED,證明正在擴容,那么協助擴容else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;// 保證這個位置的桶元素插入時線程安全的,即對桶加鎖// 不影響其他元素的桶位置插入;既保證安全,又不影響效率// hashtable則是鎖了整個數組synchronized (f) {// 保證還在該位置,比如變成樹或者擴容之后,位置改變了if (tabAt(tab, i) == f) {// 判斷hash值大于0 ,就表示當前情況下該位置桶還是鏈式結構if (fh >= 0) {binCount = 1;// 遍歷鏈表for (Node<K,V> e = f;; ++binCount) {K ek;// 如果在鏈表中找到了put中key值,那么就替換if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;// 完成替換之后就跳出循環break;}// 如果沒有找到該值,就在使用尾插法將Entry插入鏈表的尾部Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}// 當前位置為樹結構,將元素添加到紅黑樹中else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}// 以上就是ConcurrentHashMap添加元素的安全操作// 從上面代碼可以得到,ConcurrentHashMap是通過對桶加鎖而不是對整個數組加鎖,對效率有提高if (binCount != 0) {// 如果元素個數大于等于8且數組長度大于64,就變成了樹if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null; }2.2 putVal源碼程序流程圖
2.3 putVal閱讀源碼總結
通過源碼分析可以得到,ConcurrentHashMap插入元素大體來說和HashMap差不多,不同的是,ConcurrentHashMap添加了不少同步操作,如圖紅色標記,這樣就實現了同步安全。
不同于HashTable的是,ConcurrentHashMap主要采用的是CAS自旋鎖,提高了效率。
此外,ConcurrentHashMap鎖的對象是數組中的每一個桶而不是整個數組,這就意味著,在多線程操作的時候,同一個數組不同的桶之間操作不影響,也就是說,同一個時間,可以有多個線程對數組有插入元素的操作,提高了效率。
2.4 initTable源碼分析
// 數組初始化 private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;// table 表示初始數組// 進行cas+自旋鎖,保證線程安全,對數進行初始化while ((tab = table) == null || tab.length == 0) {// 如果sizeCtl小于0,說明此時正在初始化,讓出cpuif ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spin// cas修改sizeCtl的值為-1,如果修改成功,進行數組初始化,如果修改失敗,繼續自選// 就是sc和SIZECTL對比else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {// 完成yield后,sc不是小于0if ((tab = table) == null || tab.length == 0) {// 如果sizeCtl值為0,取默認長度16;否則取sizeCtl中的值int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")// 基于初始長度,構造數組對象Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;// 計算擴容閾值,并賦值給sc,就是0.75*nsc = n - (n >>> 2);}} finally {// 最后將擴容閾值賦值給sizeCtlsizeCtl = sc;}break;}}return tab; }2.5 initTable源碼程序流程圖
2.6 JDK8中ConcurrentHashMap添加安全總結
通過對initTable源碼和putVal源碼的閱讀比較,發現二者在實現同步的過程中都采用cas自旋鎖來實現同步的,極大提高了資源。
在源碼中大量使用了 Unsafe.compareAndSwapInt(Object 0, long offset, int expected, int x),此方法是Java的native方法,并不由Java語言實現。
方法的作用是,讀取傳入對象o在內存中偏移量為offset位置的值與期望值expected作比較。相等就把x值賦值給offset位置的值。方法返回true。不相等,就取消賦值,方法返回false。這也是CAS的思想,及比較并交換。用于保證并發時的無鎖并發的安全性。
CAS程序流程圖
三、JDK8擴容安全
3.1 transfer源碼分析
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;// 如果是多cpu,那么每個線程劃分任務,最小任務量是16個桶位的遷移if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // MIN_TRANSFER_STRIDE = 16// 如果是擴容線程,此時新數組為null// 計算最少任務量if (nextTab == null) { // initiatingtry {@SuppressWarnings("unchecked")// 創建新的數組,數組長度為原來數組的兩倍Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //左移0.5倍,右移2倍nextTab = nt;} catch (Throwable ex) { // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;// 記錄線程開始遷移的桶位,從后往前遷移transferIndex = n;}// 記錄新數組的末尾int nextn = nextTab.length;// 如果桶位已經被遷移,會用ForwardingNode占位(這個節點的hash值為-1--MOVED)ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);boolean advance = true;boolean finishing = false; // to ensure sweep before committing nextTab// 自旋,i記錄當前正在遷移桶位的索引值for (int i = 0, bound = 0;;) {Node<K,V> f; int fh;// 計算每一個線程到底負責多少元素的遷移while (advance) {int nextIndex, nextBound;// bound記錄下一次任務遷移的開始桶位// --i >= bound 成立表示當前線程分配的遷移任務還沒有完成if (--i >= bound || finishing)advance = false;// 沒有元素需要遷移 -- 后續會去將擴容線程數減1,并判斷擴容是否完成else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}// 主要計算是在這里,這里的遷移是從后往前遷移// 計算下一次任務遷移的開始桶位,并將這個值賦值給transferIndexelse if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}// 分配任務,這一段標識任務是否做完if (i < 0 || i >= n || i + n >= nextn) {int sc;// 是否所有線程都做完了// 擴容結束后,保存新數組,并重新計算擴容閾值,賦值給sizeCtlif (finishing) {nextTable = null;table = nextTab;// 這一行代碼是說2 * n - 0.5 * n = 1.5 * n = 0.75 * 2n,位運算效率高sizeCtl = (n << 1) - (n >>> 1);return;}// 判斷擴容操作是否完成// 擴容任務線程數減1if (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}}//當前遷移的桶位沒有元素,直接在該位置添加一個fwd節點else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);//當前節點已經被遷移else if ((fh = f.hash) == MOVED)advance = true; // already processed// 非空且為被遷移else {// 如果正在做遷移,其他線程不能在當前位置上添加元素synchronized (f) {if (tabAt(tab, i) == f) {Node<K,V> ln, hn;if (fh >= 0) {int runBit = fh & n;Node<K,V> lastRun = f;for (Node<K,V> 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<K,V> 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<K,V>(ph, pk, pv, ln);elsehn = new Node<K,V>(ph, pk, pv, hn);}setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}else if (f instanceof TreeBin) {TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> lo = null, loTail = null;TreeNode<K,V> hi = null, hiTail = null;int lc = 0, hc = 0;for (Node<K,V> e = t.first; e != null; e = e.next) {int h = e.hash;TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null);if ((h & n) == 0) {if ((p.prev = loTail) == null)lo = p;elseloTail.next = p;loTail = p;++lc;}else {if ((p.prev = hiTail) == null)hi = p;elsehiTail.next = p;hiTail = p;++hc;}}ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :(hc != 0) ? new TreeBin<K,V>(lo) : t;hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :(lc != 0) ? new TreeBin<K,V>(hi) : t;setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}}}}} }3.2 擴容安全圖解
3.3 JDK8中ConcurrentHashMap擴容安全總結
如果是多核CPU的前提下,那么每個線程劃分任務,最小任務量是16個桶位的遷移。
在遷移過程中,通過自旋來控制整個過程的持續性,直到所有線程完成擴容任務。
對于桶位來說,如果桶位已經被遷移,會用ForwardingNode占位(這個節點的hash值為-1–MOVED)。使用advance標記線程是否完成擴容。那么,如果說當前遷移的桶位沒有元素,那該怎么辦呢?在源碼中是直接在該位置添加一個fwd節點
在擴容的時候,需要計算下一次任務遷移的開始桶位,并將這個值賦值給transferIndex,這個過程是用cas完成的。
如果當前桶位需要被遷移,就好比在當前桶位插入數據一樣,需要使用synchronized關鍵字來為該桶位加鎖,保證多線程安全。
四、JDK8多線程擴容效率改進
多線程協助擴容的操作會在兩個地方被觸發:
① 當添加元素時,發現添加的元素對應的桶位為fwd節點,就會先去協助擴容,然后再添加元素
② 當添加完元素后,判斷當前元素個數達到了擴容閾值,此時發現sizeCtl的值小于0,并且新數組不為空,這個時候,會去協助擴容
4.1 方案1:putVal源碼分析
final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin}//發現此處為fwd節點,協助擴容,擴容結束后,再循環回來添加元素else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);//省略代碼4.2 方案2:helpTransfer源碼分析
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {Node<K,V>[] nextTab; int sc;if (tab != null && (f instanceof ForwardingNode) &&(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {int rs = resizeStamp(tab.length);while (nextTab == nextTable && table == tab &&(sc = sizeCtl) < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {//擴容,傳遞一個不是null的nextTabtransfer(tab, nextTab);break;}}return nextTab;}return table; }4.3 JDK中ConcurrentHashMap擴容效率提高圖解
五、集合長度的累計方式
5.1 addCount源碼分析
① CounterCell數組不為空,優先利用數組中的CounterCell記錄數量
② 如果數組為空,嘗試對baseCount進行累加,失敗后,會執行fullAddCount邏輯
③ 如果是添加元素操作,會繼續判斷是否需要擴容
private final void addCount(long x, int check) {CounterCell[] as; long b, s;// 一開始counterCells不為空,所以前面一個判斷不成立if ((as = counterCells) != null ||// 在單線程的條件下,將s=b+1,加成功之后compareAndSwapLong返回true,取反為false,所以不進入代碼,直接加入成功// 當有兩個以上的線程進入這個位置,那么必然有一個線程加成功,其他線程加失敗,所以返回false,取反返回true,進入代碼塊!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {CounterCell a; long v; int m;boolean uncontended = true;// 數組為空,或數組不存在(長度小于0)if (as == null || (m = as.length - 1) < 0 ||(a = as[ThreadLocalRandom.getProbe() & m]) == null ||!(uncontended =U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {fullAddCount(x, uncontended);return;}if (check <= 1)return;// sumCount是獲取當前數組長度s = sumCount();}// 移除或替換元素if (check >= 0) {Node<K,V>[] tab, nt; int n, sc;while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&(n = tab.length) < MAXIMUM_CAPACITY) {int rs = resizeStamp(n);// 第一次sc不會小于0if (sc < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0)break;// 協助擴容,nt指的是新的數組if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))transfer(tab, nt);}// 移完之后,最高位是1,所以變為sc為負數,所以sizeCtl也小于0else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))// 擴容transfer(tab, null);s = sumCount();}} }5.2 fullAddCount源碼分析
① 當CounterCell數組不為空,優先對CounterCell數組中的CounterCell的value累加
② 當CounterCell數組為空,會去創建CounterCell數組,默認長度為2,并對數組中的CounterCell的value累加
③ 當數組為空,并且此時有別的線程正在創建數組,那么嘗試對baseCount做累加,成功即返回,否則自旋
// See LongAdder version for explanation private final void fullAddCount(long x, boolean wasUncontended) {int h;if ((h = ThreadLocalRandom.getProbe()) == 0) {ThreadLocalRandom.localInit(); // force initializationh = ThreadLocalRandom.getProbe();wasUncontended = true;}boolean collide = false; // True if last slot nonempty// 整個過程沒有加鎖動作,只是使用cas+自旋的動作for (;;) {CounterCell[] as; CounterCell a; int n; long v;// 數組不為空if ((as = counterCells) != null && (n = as.length) > 0) {// 創建CounterCell對象,并對CounterCell中的value累加值,若成功,則結束循環if ((a = as[(n - 1) & h]) == null) {if (cellsBusy == 0) { // Try to attach new CellCounterCell r = new CounterCell(x); // Optimistic createif (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean created = false;try { // Recheck under lockCounterCell[] rs; int m, j;if ((rs = counterCells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {rs[j] = r;created = true;}} finally {cellsBusy = 0;}if (created)break;continue; // Slot is now non-empty}}collide = false;}// 如果wasUncontended==false,那么rehash,然后asUncontended設置為trueelse if (!wasUncontended) // CAS already known to failwasUncontended = true; // Continue after rehash// 桶位value累加成功結束循環else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))break;// 有別的線程對數組擴容/數組容量達到最大值就是cpu的核數,并rehashelse if (counterCells != as || n >= NCPU)collide = false; // At max size or stale// collide=true,并rehashelse if (!collide)collide = true;// 數組進行擴容,成功后繼續循環else if (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {try {if (counterCells == as) {// Expand table unless staleCounterCell[] rs = new CounterCell[n << 1];for (int i = 0; i < n; ++i)rs[i] = as[i];counterCells = rs;}} finally {cellsBusy = 0;}collide = false;continue; // Retry with expanded table}h = ThreadLocalRandom.advanceProbe(h);}// 數組為空,先創建數組else if (cellsBusy == 0 && counterCells == as &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init = false;try { // Initialize tableif (counterCells == as) {CounterCell[] rs = new CounterCell[2];rs[h & 1] = new CounterCell(x);counterCells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}// 數組正在被創建,且數組為空,baseCount++else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))break; // Fall back on using base} }六、jdk1.8集合長度獲取
6.1 size源碼
public int size() {long n = sumCount();return ((n < 0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n); }6.2 sumCount源碼
final long sumCount() {CounterCell[] as = counterCells; CounterCell a;//獲取baseCount的值long sum = baseCount;if (as != null) {//遍歷CounterCell數組,累加每一個CounterCell的value值for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum; }注意:這個方法并不是線程安全的
七、JDK7中ConcurrentHashMap
ConcurrentHashMap與HashMap思路差不多,但是ConcurrentHashMap支持并發操作。
整個ConcurrentHashMap由一個個Segment組成,其作用就是用于分段鎖。
Segment繼承自ReentrantLock加鎖。
所以每一個加鎖鎖住的是一個個segment,確保每個segment是安全的,那么全局也是安全的。
與HashMap類似的,Java 7 中的ConcurrentHashMap的底層也是數組+鏈表。
Java 8 中的ConcurrentHashMap則是數組+鏈表+紅黑樹的結構實現
ConcurrentHashMap的ConcurrentLevel(并發級別)默認有16個Segments,理論上最多可以同時支持16個線程并發,只要它們的操作分布在不同的Segment上。
這個值(ConcurrentLevel)最初可以設置為其他值,但一旦初始化后,就不可以再擴容。
細化到Segment內部,其實每一個Segment相當于一個HashMap,不過要保證線程安全,所以要更麻煩些。
八、相關面試題
【Java自頂向下】ConcurrentHashMap面試題(2021最新版)
總結
以上是生活随笔為你收集整理的【Java自顶向下】试手小公司,面试官问我ConcurrentHashMap,我微微一笑……的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java自顶向下】Concurrent
- 下一篇: 【程序人生】不想一辈子做底层码农?快来看