新聞中心
Java 7的ConcurrenHashMap的源碼我建議大家都看看,那個(gè)版本的源碼就是Java多線程編程的教科書(shū)。在Java 7的源碼中,作者對(duì)悲觀鎖的使用非常謹(jǐn)慎,大多都轉(zhuǎn)換為自旋鎖加volatile獲得相同的語(yǔ)義,即使最后迫不得已要用,作者也會(huì)通過(guò)各種技巧減少鎖的臨界區(qū)。在上一篇文章中我們也有講到,自旋鎖在臨界區(qū)比較小的時(shí)候是一個(gè)較優(yōu)的選擇是因?yàn)樗苊饬司€程由于阻塞而切換上下文,但本質(zhì)上它也是個(gè)鎖,在自旋等待期間只有一個(gè)線程能進(jìn)入臨界區(qū),其他線程只會(huì)自旋消耗CPU的時(shí)間片。Java 8中ConcurrentHashMap的實(shí)現(xiàn)通過(guò)一些巧妙的設(shè)計(jì)和技巧,避開(kāi)了自旋鎖的局限,提供了更高的并發(fā)性能。如果說(shuō)Java 7版本的源碼是在教我們?nèi)绾螌⒈^鎖轉(zhuǎn)換為自旋鎖,那么在Java 8中我們甚至可以看到如何將自旋鎖轉(zhuǎn)換為無(wú)鎖的方法和技巧。

新區(qū)網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、響應(yīng)式網(wǎng)站設(shè)計(jì)等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站成立于2013年到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
把書(shū)讀薄
image
圖片來(lái)源:https://www.zhenchao.org/2019/01/31/java/cas-based-concurrent-hashmap/
在開(kāi)始本文之前,大家首先在心里還是要有這樣的一張圖,如果有同學(xué)對(duì)HashMap比較熟悉,那這張圖也應(yīng)該不會(huì)陌生。事實(shí)上在整體的數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)上Java 8的ConcurrentHashMap和HashMap基本上是一致的。
Java 7中ConcurrentHashMap為了提升性能使用了很多的編程技巧,但是引入Segment的設(shè)計(jì)還是有很大的改進(jìn)空間的,Java 7中ConcurrrentHashMap的設(shè)計(jì)有下面這幾個(gè)可以改進(jìn)的點(diǎn):
1. Segment在擴(kuò)容的時(shí)候非擴(kuò)容線程對(duì)本Segment的寫操作時(shí)都要掛起等待的
2. 對(duì)ConcurrentHashMap的讀操作需要做兩次哈希尋址,在讀多寫少的情況下其實(shí)是有額外的性能損失的
3. 盡管size()方法的實(shí)現(xiàn)中先嘗試無(wú)鎖讀,但是如果在這個(gè)過(guò)程中有別的線程做寫入操作,那調(diào)用size()的這個(gè)線程就會(huì)給整個(gè)ConcurrentHashMap加鎖,這是整個(gè)ConcurrrentHashMap唯一一個(gè)全局鎖,這點(diǎn)對(duì)底層的組件來(lái)說(shuō)還是有性能隱患的
4. 極端情況下(比如客戶端實(shí)現(xiàn)了一個(gè)性能很差的哈希函數(shù))get()方法的復(fù)雜度會(huì)退化到O(n)。
針對(duì)1和2,在Java 8的設(shè)計(jì)是廢棄了Segment的使用,將悲觀鎖的粒度降低至桶維度,因此調(diào)用get的時(shí)候也不需要再做兩次哈希了。size()的設(shè)計(jì)是Java 8版本中最大的亮點(diǎn),我們?cè)诤竺娴奈恼轮袝?huì)詳細(xì)說(shuō)明。至于紅黑樹(shù),這篇文章仍然不做過(guò)多闡述。接下來(lái)的篇幅會(huì)深挖細(xì)節(jié),把書(shū)讀厚,涉及到的模塊有:初始化,put方法, 擴(kuò)容方法transfer以及size()方法,而其他模塊,比如hash函數(shù)等改變較小,故不再深究。
準(zhǔn)備知識(shí)
ForwardingNode
- static final class ForwardingNode
extends Node { - final Node
[] nextTable; - ForwardingNode(Node
[] tab) { - // MOVED = -1,F(xiàn)orwardingNode的哈希值為-1
- super(MOVED, null, null, null);
- this.nextTable = tab;
- }
- }
除了普通的Node和TreeNode之外,ConcurrentHashMap還引入了一個(gè)新的數(shù)據(jù)類型ForwardingNode,我們這里只展示他的構(gòu)造方法,F(xiàn)orwardingNode的作用有兩個(gè):
- 在動(dòng)態(tài)擴(kuò)容的過(guò)程中標(biāo)志某個(gè)桶已經(jīng)被復(fù)制到了新的桶數(shù)組中
- 如果在動(dòng)態(tài)擴(kuò)容的時(shí)候有g(shù)et方法的調(diào)用,則ForwardingNode將會(huì)把請(qǐng)求轉(zhuǎn)發(fā)到新的桶數(shù)組中,以避免阻塞get方法的調(diào)用,F(xiàn)orwardingNode在構(gòu)造的時(shí)候會(huì)將擴(kuò)容后的桶數(shù)組nextTable保存下來(lái)。
UNSAFE.compareAndSwap***
這是在Java 8版本的ConcurrentHashMap實(shí)現(xiàn)CAS的工具,以int類型為例其方法定義如下:
- /**
- * Atomically update Java variable to x if it is currently
- * holding expected.
- * @return true if successful
- */
- public final native boolean compareAndSwapInt(Object o, long offset,
- int expected,
- int x);
相應(yīng)的語(yǔ)義為:
如果對(duì)象o起始地址偏移量為offset的值等于expected,則將該值設(shè)為x,并返回true表明更新成功,否則返回false,表明CAS失敗
初始化
- public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
- if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) // 檢查參數(shù)
- throw new IllegalArgumentException();
- if (initialCapacity < concurrencyLevel)
- initialCapacity = concurrencyLevel;
- long size = (long)(1.0 + (long)initialCapacity / loadFactor);
- int cap = (size >= (long)MAXIMUM_CAPACITY) ?
- MAXIMUM_CAPACITY : tableSizeFor((int)size); // tableSizeFor,求不小于size的 2^n的算法,jdk1.8的HashMap中說(shuō)過(guò)
- this.sizeCtl = cap;
- }
即使是最復(fù)雜的一個(gè)初始化方法代碼也是比較簡(jiǎn)單的,這里我們只需要注意兩個(gè)點(diǎn):
- concurrencyLevel在Java 7中是Segment數(shù)組的長(zhǎng)度,由于在Java 8中已經(jīng)廢棄了Segment,因此concurrencyLevel只是一個(gè)保留字段,無(wú)實(shí)際意義
- sizeCtl這個(gè)值第一次出現(xiàn),這個(gè)值如果等于-1則表明系統(tǒng)正在初始化,如果是其他負(fù)數(shù)則表明系統(tǒng)正在擴(kuò)容,在擴(kuò)容時(shí)sizeCtl二進(jìn)制的低十六位等于擴(kuò)容的線程數(shù)加一,高十六位(除符號(hào)位之外)包含桶數(shù)組的大小信息
put方法
- public V put(K key, V value) {
- return putVal(key, value, false);
- }
put方法將調(diào)用轉(zhuǎn)發(fā)到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
[] tab = table;;) { - Node
f; int n, i, fh; - // 【A】延遲初始化
- if (tab == null || (n = tab.length) == 0)
- tab = initTable();
- // 【B】當(dāng)前桶是空的,直接更新
- else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
- if (casTabAt(tab, i, null,
- new Node
(hash, key, value, null))) - break; // no lock when adding to empty bin
- }
- // 【C】如果當(dāng)前的桶的第一個(gè)元素是一個(gè)ForwardingNode節(jié)點(diǎn),則該線程嘗試加入擴(kuò)容
- else if ((ffh = f.hash) == MOVED)
- tab = helpTransfer(tab, f);
- // 【D】否則遍歷桶內(nèi)的鏈表或樹(shù),并插入
- else {
- // 暫時(shí)折疊起來(lái),后面詳細(xì)看
- }
- }
- // 【F】流程走到此處,說(shuō)明已經(jīng)put成功,map的記錄總數(shù)加一
- addCount(1L, binCount);
- return null;
- }
從整個(gè)代碼結(jié)構(gòu)上來(lái)看流程還是比較清楚的,我用括號(hào)加字母的方式標(biāo)注了幾個(gè)非常重要的步驟,put方法依然牽扯出很多的知識(shí)點(diǎn)
桶數(shù)組的初始化
- private final Node
[] initTable() { - Node
[] tab; int sc; - while ((tab = table) == null || tab.length == 0) {
- if ((sc = sizeCtl) < 0)
- // 說(shuō)明已經(jīng)有線程在初始化了,本線程開(kāi)始自旋
- Thread.yield(); // lost initialization race; just spin
- else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
- // CAS保證只有一個(gè)線程能走到這個(gè)分支
- try {
- if ((tab = table) == null || tab.length == 0) {
- int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
- @SuppressWarnings("unchecked")
- Node
[] nt = (Node [])new Node,?>[n]; - tabtable = tab = nt;
- // sc = n - n/4 = 0.75n
- sc = n - (n >>> 2);
- }
- } finally {
- // 恢復(fù)sizeCtl > 0相當(dāng)于釋放鎖
- sizeCtl = sc;
- }
- break;
- }
- }
- return tab;
- }
在初始化桶數(shù)組的過(guò)程中,系統(tǒng)如何保證不會(huì)出現(xiàn)并發(fā)問(wèn)題呢,關(guān)鍵點(diǎn)在于自旋鎖的使用,當(dāng)有多個(gè)線程都執(zhí)行initTable方法的時(shí)候,CAS可以保證只有一個(gè)線程能夠進(jìn)入到真正的初始化分支,其他線程都是自旋等待。這段代碼中我們關(guān)注三點(diǎn)即可:
- 依照前文所述,當(dāng)有線程開(kāi)始初始化桶數(shù)組時(shí),會(huì)通過(guò)CAS將sizeCtl置為-1,其他線程以此為標(biāo)志開(kāi)始自旋等待
- 當(dāng)桶數(shù)組初始化結(jié)束后將sizeCtl的值恢復(fù)為正數(shù),其值等于0.75倍的桶數(shù)組長(zhǎng)度,這個(gè)值的含義和之前HashMap中的THRESHOLD一致,是系統(tǒng)觸發(fā)擴(kuò)容的臨界點(diǎn)
- 在finally語(yǔ)句中對(duì)sizeCtl的操作并沒(méi)有使用CAS是因?yàn)镃AS保證只有一個(gè)線程能夠執(zhí)行到這個(gè)地方
添加桶數(shù)組第一個(gè)元素
- static final
Node tabAt(Node [] tab, int i) { - return (Node
)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); - }
- static final
boolean casTabAt(Node [] tab, int i, - Node
c, Node v) { - return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
- }
put方法的第二個(gè)分支會(huì)用tabAt判斷當(dāng)前桶是否是空的,如果是則會(huì)通過(guò)CAS寫入,tabAt通過(guò)UNSAFE接口會(huì)拿到桶中的最新元素,casTabAt通過(guò)CAS保證不會(huì)有并發(fā)問(wèn)題,如果CAS失敗,則通過(guò)循環(huán)再進(jìn)入其他分支
判斷是否需要新增線程擴(kuò)容
- final Node
[] helpTransfer(Node [] tab, Node f) { - Node
[] nextTab; int sc; - if (tab != null && (f instanceof ForwardingNode) &&
- (nextTab = ((ForwardingNode
)f).nextTable) != null) { - int rs = resizeStamp(tab.length);
- while (nextTab == nextTable && table == tab &&
- (sc = sizeCtl) < 0) {
- // RESIZE_STAMP_SHIFT = 16
- if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
- sc == rs + MAX_RESIZERS || transferIndex <= 0)
- break;
- // 這里將sizeCtl的值自增1,表明參與擴(kuò)容的線程數(shù)量+1
- if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
- transfer(tab, nextTab);
- break;
- }
- }
- return nextTab;
- }
- return table;
- }
在這個(gè)地方我們就要詳細(xì)說(shuō)下sizeCtl這個(gè)標(biāo)志位了,臨時(shí)變量rs由resizeStamp這個(gè)方法返回
- static final int resizeStamp(int n) {
- // RESIZE_STAMP_BITS = 16
- return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
- }
因?yàn)槿雲(yún)是一個(gè)int類型的值,所有Integer.numberOfLeadingZeros(n)的返回值介于0到32之間,如果轉(zhuǎn)換成二進(jìn)制
- Integer.numberOfLeadingZeros(n)的最大值是:00000000 00000000 00000000 00100000
- Integer.numberOfLeadingZeros(n)的最小值是:00000000 00000000 00000000 00000000
因此resizeStampd的返回值也就介于00000000 00000000 10000000 00000000到00000000 00000000 10000000 00100000之間,從這個(gè)返回值的范圍可以看出來(lái)resizeStamp的返回值高16位全都是0,是不包含任何信息的。因此在ConcurrrentHashMap中,會(huì)把resizeStamp的返回值左移16位拼到sizeCtl中,這就是為什么sizeCtl的高16位包含整個(gè)Map大小的原理。有了這個(gè)分析,這段代碼中比較長(zhǎng)的if判斷也就能看懂了
- if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
- sc == rs + MAX_RESIZERS || transferIndex <= 0)
- break;
- (sc >>> RESIZE_STAMP_SHIFT) != rs保證所有線程要基于同一個(gè)舊的桶數(shù)組擴(kuò)容
- transferIndex <= 0已經(jīng)有線程完成擴(kuò)容任務(wù)了
至于sc == rs + 1 || sc == rs + MAX_RESIZERS這兩個(gè)判斷條件如果是細(xì)心的同學(xué)一定會(huì)覺(jué)得難以理解,這個(gè)地方確實(shí)是JDK的一個(gè)BUG,這個(gè)BUG已經(jīng)在JDK 12中修復(fù),詳細(xì)情況可以參考一下Oracle的官網(wǎng):https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8214427,這兩個(gè)判斷條件應(yīng)該寫成這樣:sc == (rs << RESIZE_STAMP_SHIFT) + 1 || sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS,因?yàn)橹苯颖容^rs和sc是沒(méi)有意義的,必須要有移位操作。它表達(dá)的含義是
- sc == (rs << RESIZE_STAMP_SHIFT) + 1當(dāng)前擴(kuò)容的線程數(shù)為0,即已經(jīng)擴(kuò)容完成了,就不需要再新增線程擴(kuò)容
- sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS參與擴(kuò)容的線程數(shù)已經(jīng)到了最大,就不需要再新增線程擴(kuò)容
真正擴(kuò)容的邏輯在transfer方法中,我們后面會(huì)詳細(xì)看,不過(guò)有個(gè)小細(xì)節(jié)可以提前注意,如果nextTable已經(jīng)初始化了,transfer會(huì)返回nextTable的的引用,后續(xù)可以直接操作新的桶數(shù)組。
插入新值
如果桶數(shù)組已經(jīng)初始化好了,該擴(kuò)容的也擴(kuò)容了,并且根據(jù)哈希定位到的桶中已經(jīng)有元素了,那流程就跟普通的HashMap一樣了,唯一一點(diǎn)不同的就是,這時(shí)候要給當(dāng)前的桶加鎖,且看代碼:
- 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
[] tab = table;;) { - Node
f; int n, i, fh; - if (tab == null || (n = tab.length) == 0)// 折疊
- else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 折疊}
- else if ((ffh = f.hash) == MOVED)// 折疊
- else {
- V oldVal = null;
- synchronized (f) {
- // 要注意這里這個(gè)不起眼的判斷條件
- if (tabAt(tab, i) == f) {
- if (fh >= 0) { // fh>=0的節(jié)點(diǎn)是鏈表,否則是樹(shù)節(jié)點(diǎn)或者ForwardingNode
- binCount = 1;
- for (Node
e = f;; ++binCount) { - K ek;
- if (e.hash == hash &&
- ((eek = e.key) == key ||
- (ek != null && key.equals(ek)))) {
- oldVal = e.val; // 如果鏈表中有值了,直接更新
- if (!onlyIfAbsent)
- e.val = value;
- break;
- }
- Node
pred = e; - if ((ee = e.next) == null) {
- // 如果流程走到這里,則說(shuō)明鏈表中還沒(méi)值,直接連接到鏈表尾部
- pred.next = new Node
(hash, key, value, null); - break;
- }
- }
- }
- // 紅黑樹(shù)的操作先略過(guò)
- }
- }
- }
- }
- // put成功,map的元素個(gè)數(shù)+1
- addCount(1L, binCount);
- return null;
- }
這段代碼中要特備注意一個(gè)不起眼的判斷條件(上下文在源碼上已經(jīng)標(biāo)注出來(lái)了):tabAt(tab, i) == f,這個(gè)判斷的目的是為了處理調(diào)用put方法的線程和擴(kuò)容線程的競(jìng)爭(zhēng)。因?yàn)閟ynchronized是阻塞鎖,如果調(diào)用put方法的線程恰好和擴(kuò)容線程同時(shí)操作同一個(gè)桶,且調(diào)用put方法的線程競(jìng)爭(zhēng)鎖失敗,等到該線程重新獲取到鎖的時(shí)候,當(dāng)前桶中的元素就會(huì)變成一個(gè)ForwardingNode,那就會(huì)出現(xiàn)tabAt(tab, i) != f的情況。
多線程動(dòng)態(tài)擴(kuò)容
- private final void transfer(Node
[] tab, Node [] nextTab) { - int n = tab.length, stride;
- if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
- stride = MIN_TRANSFER_STRIDE; // subdivide range
- if (nextTab == null) { // 初始化新的桶數(shù)組
- 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;
- }
- nextTabnextTable = nextTab;
- transferIndex = n;
- }
- int nextn = nextTab.length;
- ForwardingNode
fwd = new ForwardingNode (nextTab); - boolean advance = true;
- boolean finishing = false; // to ensure sweep before committing nextTab
- for (int i = 0, bound = 0;;) {
- Node
f; int fh; - while (advance) {
- int nextIndex, nextBound;
- if (--i >= bound || finishing)
- advance = false;
- else if ((nextIndex = transferIndex) <= 0) {
- i = -1;
- advance = false;
- }
- else 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;
- if (finishing) {
- nextTable = null;
- table = nextTab;
- sizeCtl = (n << 1) - (n >>> 1);
- return;
- }
- if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
- // 判斷是會(huì)否是最后一個(gè)擴(kuò)容線程
- if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
- return;
- finishing = advance = true;
- i = n; // recheck before commit
- }
- }
- else if ((f = tabAt(tab, i)) == null)
- advance = casTabAt(tab, i, null, fwd);
- else if ((ffh = f.hash) == MOVED) // 只有最后一個(gè)擴(kuò)容線程才有機(jī)會(huì)執(zhí)行這個(gè)分支
- advance = true; // already processed
- else { // 復(fù)制過(guò)程與HashMap類似,這里不再贅述
- synchronized (f) {
- // 折疊
- }
- }
- }
- }
在深入到源碼細(xì)節(jié)之前我們先根據(jù)下圖看一下在Java 8中ConcurrentHashMap擴(kuò)容的幾個(gè)特點(diǎn):
- 新的桶數(shù)組nextTable是原先桶數(shù)組長(zhǎng)度的2倍,這與之前HashMap一致
- 參與擴(kuò)容的線程也是分段將table中的元素復(fù)制到新的桶數(shù)組nextTable中
- 桶一個(gè)桶數(shù)組中的元素在新的桶數(shù)組中均勻的分布在兩個(gè)桶中,桶下標(biāo)相差n(舊的桶數(shù)組的長(zhǎng)度),這一點(diǎn)依然與HashMap保持一致
image-20210424202636495
各個(gè)線程之間如何通力協(xié)作
先看一個(gè)關(guān)鍵的變量transferIndex,這是一個(gè)被volatile修飾的變量,這一點(diǎn)可以保證所有線程讀到的一定是最新的值。
- private transient volatile int transferIndex;
這個(gè)值會(huì)被第一個(gè)參與擴(kuò)容的線程初始化,因?yàn)橹挥械谝粋€(gè)參與擴(kuò)容的線程才滿足條件nextTab == null
- 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;
- }
- nextTabnextTable = nextTab;
- transferIndex = n;
- }
在了解了transferIndex屬性的基礎(chǔ)上,上面的這個(gè)循環(huán)就好理解了
- while (advance) {
- int nextIndex, nextBound;
- // 當(dāng)bound <= i <= transferIndex的時(shí)候i自減跳出這個(gè)循環(huán)繼續(xù)干活
- if (--i >= bound || finishing)
- advance = false;
- // 擴(kuò)容的所有任務(wù)已經(jīng)被認(rèn)領(lǐng)完畢,本線程結(jié)束干活
- else if ((nextIndex = transferIndex) <= 0) {
- i = -1;
- advance = false;
- }
- // 否則認(rèn)領(lǐng)新的一段復(fù)制任務(wù),并通過(guò)`CAS`更新transferIndex的值
- else if (U.compareAndSwapInt
- (this, TRANSFERINDEX, nextIndex,
- nextBound = (nextIndex > stride ?
- nextIndex - stride : 0))) {
- bound = nextBound;
- i = nextIndex - 1;
- advance = false;
- }
- }
transferIndex就像是一個(gè)游標(biāo),每個(gè)線程認(rèn)領(lǐng)一段復(fù)制任務(wù)的時(shí)候都會(huì)通過(guò)CAS將其更新為transferIndex - stride, CAS可以保證transferIndex可以按照stride這個(gè)步長(zhǎng)降到0。
最后一個(gè)擴(kuò)容線程需要二次確認(rèn)?
對(duì)于每一個(gè)擴(kuò)容線程,for循環(huán)的變量i代表要復(fù)制的桶的在桶數(shù)組中的下標(biāo),這個(gè)值的上限和下限通過(guò)游標(biāo)transferIndex和步長(zhǎng)stride計(jì)算得來(lái),當(dāng)i減小為負(fù)數(shù),則說(shuō)明當(dāng)前擴(kuò)容線程完成了擴(kuò)容任務(wù),這時(shí)候流程會(huì)走到這個(gè)分支:
- // i >= n || i + n >= nextn現(xiàn)在看來(lái)取不到
- if (i < 0 || i >= n || i + n >= nextn) {
- int sc;
- if (finishing) { // 【A】完成整個(gè)擴(kuò)容過(guò)程
- nextTable = null;
- table = nextTab;
網(wǎng)站欄目:Java 8 ConcurrentHashMap源碼中竟然隱藏著兩個(gè)Bug
URL標(biāo)題:http://www.dlmjj.cn/article/djoppch.html


咨詢
建站咨詢
