轉(zhuǎn):ConcurrentHashMap JDK 8 源碼分析

文章

http://www.reibang.com/p/289cf670733e 猜煮,主要講 JDK 8

http://www.reibang.com/p/487d00afe6ca 彰阴, 將擴(kuò)容操作

http://www.reibang.com/p/7db1ac8395ee 脓诡, 1.7 匪凉, 1.8 都有講

在 1.8 版本以前,ConcurrentHashMap 采用分段鎖的概念剪勿,使鎖更加細(xì)化撞芍,但是 1.8 已經(jīng)改變了這種思路,而是利用 CAS + Synchronized 來保證并發(fā)更新的安全翩瓜,當(dāng)然底層采用數(shù)組 + 鏈表 + 紅黑樹的存儲(chǔ)結(jié)構(gòu)受扳。

數(shù)據(jù)結(jié)構(gòu)

重要字段

/**
 * races. Updated via CAS.
 * 記錄容器的容量大小,通過CAS更新
 */
private transient volatile long baseCount;

/**
 * 這個(gè)sizeCtl是volatile的兔跌,那么他是線程可見的勘高,一個(gè)思考:它是所有修改都在CAS中進(jìn)行,但是sizeCtl為什么不設(shè)計(jì)成LongAdder(jdk8出現(xiàn)的)類型呢坟桅?
 * 或者設(shè)計(jì)成AtomicLong(在高并發(fā)的情況下比LongAdder低效)华望,這樣就能減少自己操作CAS了。
 *
 * 來看下注釋仅乓,當(dāng)sizeCtl小于0說明有多個(gè)線程正則等待擴(kuò)容結(jié)果赖舟,參考transfer函數(shù)
 *
 * sizeCtl等于0是默認(rèn)值,大于0是擴(kuò)容的閥值
 */
private transient volatile int sizeCtl;

/**
 *  自旋鎖 (鎖定通過 CAS) 在調(diào)整大小和/或創(chuàng)建 CounterCells 時(shí)使用夸楣。 在CounterCell類更新value中會(huì)使用宾抓,功能類似顯示鎖和內(nèi)置鎖,性能更好
 *  在Striped64類也有應(yīng)用
 */
private transient volatile int cellsBusy;

還有最重要的節(jié)點(diǎn)類Node裕偿,注意val和next是volatile類型

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    
    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
}

下面對(duì)重點(diǎn)的常量進(jìn)行說明:

  1. table:用來存放Node節(jié)點(diǎn)數(shù)據(jù)的洞慎,默認(rèn)為null,默認(rèn)大小為16的數(shù)組嘿棘,每次擴(kuò)容時(shí)大小總是2的冪次方

  2. nextTable:擴(kuò)容時(shí)新生成的數(shù)據(jù),數(shù)組為table的兩倍

  3. Node:節(jié)點(diǎn)旭绒,保存key-value的數(shù)據(jù)結(jié)構(gòu)

  4. ForwardingNode:一個(gè)特殊的Node節(jié)點(diǎn)鸟妙,hash值為-1焦人,其中存儲(chǔ)nextTable的引用。只有table發(fā)生擴(kuò)容的時(shí)候重父,F(xiàn)orwardingNode才會(huì)發(fā)揮作用花椭,作為一個(gè)占位符放在table中表示當(dāng)前節(jié)點(diǎn)為null或則已經(jīng)被移動(dòng)

  5. sizeCtl:控制標(biāo)識(shí)符,用來控制table初始化和擴(kuò)容操作的房午,在不同的地方有不同的用途矿辽,其值也不同,所代表的含義也不同

    • 負(fù)數(shù)代表正在進(jìn)行初始化或擴(kuò)容操作

    • -1代表正在初始化

    • -N 表示有N-1個(gè)線程正在進(jìn)行擴(kuò)容操作

    • 正數(shù)或0代表hash表還沒有被初始化郭厌,這個(gè)數(shù)值表示初始化或下一次進(jìn)行擴(kuò)容的大小


重點(diǎn)內(nèi)部類

  1. Node

    Node 存儲(chǔ) key-value 鍵值對(duì)袋倔,所有插入ConcurrentHashMap的中數(shù)據(jù)都將會(huì)包裝在Node中。

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;             //帶有volatile折柠,保證可見性
        volatile Node<K,V> next;    //下一個(gè)節(jié)點(diǎn)的指針
    
    /** 不允許修改value的值 */
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }
    
    /**  賦值get()方法 */
            Node<K,V> find(int h, Object k) {
                Node<K,V> e = this;
                if (k != null) {
                    do {
                        K ek;
                        if (e.hash == h &&
                                ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                    } while ((e = e.next) != null);
                }
                return null;
            }
        }
    

    在Node內(nèi)部類中宾娜,其屬性value、next都是帶有volatile的扇售。同時(shí)其對(duì)value的setter方法進(jìn)行了特殊處理前塔,不允許直接調(diào)用其setter方法來修改value的值。最后Node還提供了find方法來賦值map.get()承冰。

  2. TreeNode

    在 1.8 的 ConcurrentHashMap 中华弓,如果鏈表的數(shù)據(jù)過長是會(huì)轉(zhuǎn)換為紅黑樹來處理。當(dāng)它并不是直接轉(zhuǎn)換困乒,而是將這些鏈表的節(jié)點(diǎn)包裝成TreeNode放在TreeBin對(duì)象中寂屏,然后由TreeBin完成紅黑樹的轉(zhuǎn)換。所以TreeNode也必須是ConcurrentHashMap的一個(gè)核心類顶燕,其為樹節(jié)點(diǎn)類凑保。源碼展示TreeNode繼承Node,且提供了findTreeNode用來查找查找hash為h涌攻,key為k的節(jié)點(diǎn)欧引。

  3. TreeBin

    該類并不負(fù)責(zé)key-value的鍵值對(duì)包裝,它用于在鏈表轉(zhuǎn)換為紅黑樹時(shí)包裝TreeNode節(jié)點(diǎn)恳谎,也就是說ConcurrentHashMap紅黑樹存放是TreeBin芝此,不是TreeNode。該類封裝了一系列的方法因痛,包括putTreeVal婚苹、lookRoot、UNlookRoot鸵膏、remove膊升、balanceInsetion、balanceDeletion谭企。

  4. ForwardingNode

    這是一個(gè)真正的輔助類廓译,該類僅僅只存活在ConcurrentHashMap擴(kuò)容操作時(shí)评肆。只是一個(gè)標(biāo)志節(jié)點(diǎn),并且指向nextTable非区,它提供find方法而已瓜挽。該類也是集成Node節(jié)點(diǎn),其hash為-1征绸,key久橙、value、next均為null管怠。

構(gòu)造函數(shù)

初始化: initTable()

ConcurrentHashMap的初始化主要由initTable()方法實(shí)現(xiàn)淆衷,在上面的構(gòu)造函數(shù)中我們可以看到,其實(shí)ConcurrentHashMap在構(gòu)造函數(shù)中并沒有做什么事排惨,僅僅只是設(shè)置了一些參數(shù)而已吭敢。其真正的初始化是發(fā)生在插入的時(shí)候,例如put暮芭、merge鹿驼、compute、computeIfAbsent辕宏、computeIfPresent操作時(shí)畜晰。其方法定義如下:

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        //sizeCtl < 0 表示有其他線程在初始化,該線程必須掛起
        if ((sc = sizeCtl) < 0)
            Thread.yield();
        // 如果該線程獲取了初始化的權(quán)利瑞筐,則用CAS將sizeCtl設(shè)置為-1凄鼻,表示本線程正在初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                // 進(jìn)行初始化
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // 下次擴(kuò)容的大小
                    sc = n - (n >>> 2); ///相當(dāng)于0.75*n 設(shè)置一個(gè)擴(kuò)容的閾值  
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

初始化方法initTable()的關(guān)鍵就在于sizeCtl,該值默認(rèn)為0聚假,如果在構(gòu)造函數(shù)時(shí)有參數(shù)傳入該值則為2的冪次方块蚌。該值如果 < 0,表示有其他線程正在初始化膘格,則必須暫停該線程峭范。如果線程獲得了初始化的權(quán)限則先將sizeCtl設(shè)置為-1,防止有其他線程進(jìn)入瘪贱,最后將sizeCtl設(shè)置0.75 * n纱控,表示擴(kuò)容的閾值。

put

核心思想依然是根據(jù)hash值計(jì)算節(jié)點(diǎn)插入在table的位置菜秦,如果該位置為空甜害,則直接插入,否則插入到鏈表或者樹中球昨。但是ConcurrentHashMap會(huì)涉及到多線程情況就會(huì)復(fù)雜很多尔店。

public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    //key、value均不能為null
    if (key == null || value == null) throw new NullPointerException();
    //計(jì)算hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // table為null,進(jìn)行初始化工作
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //如果i位置沒有節(jié)點(diǎn)闹获,則直接插入期犬,不需要加鎖
        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
        }
        // 有線程正在進(jìn)行擴(kuò)容操作河哑,則先幫助擴(kuò)容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //對(duì)該節(jié)點(diǎn)進(jìn)行加鎖處理(hash值相同的鏈表的頭節(jié)點(diǎn))避诽,對(duì)性能有點(diǎn)兒影響
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    //fh > 0 表示為鏈表,將該節(jié)點(diǎn)插入到鏈表尾部
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //hash 和 key 都一樣璃谨,替換value
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                //putIfAbsent()
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            //鏈表尾部  直接插入
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                        value, null);
                                break;
                            }
                        }
                    }
                    //樹節(jié)點(diǎn)沙庐,按照樹的插入操作進(jìn)行插入
                    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;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 如果鏈表長度已經(jīng)達(dá)到臨界值8 就需要把鏈表轉(zhuǎn)換為樹結(jié)構(gòu)
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }

    //size + 1  
    addCount(1L, binCount);
    return null;
}

按照上面的源碼,我們可以確定put整個(gè)流程如下:

  • 判空佳吞;ConcurrentHashMap的key拱雏、value都不允許為null

  • 計(jì)算hash。利用方法計(jì)算hash值底扳。

  • 遍歷table铸抑,進(jìn)行節(jié)點(diǎn)插入操作,過程如下:

    • 如果table為空衷模,則表示ConcurrentHashMap還沒有初始化鹊汛,則進(jìn)行初始化操作:initTable()

    • 根據(jù)hash值獲取節(jié)點(diǎn)的位置i,若該位置為空阱冶,則直接插入刁憋,這個(gè)過程是不需要加鎖的。計(jì)算f位置:i=(n - 1) & hash

    • 如果檢測到fh = f.hash == -1木蹬,則f是ForwardingNode節(jié)點(diǎn)至耻,表示有其他線程正在進(jìn)行擴(kuò)容操作,則幫助線程一起進(jìn)行擴(kuò)容操作

    • 如果f.hash >= 0 表示是鏈表結(jié)構(gòu)镊叁,則遍歷鏈表尘颓,如果存在當(dāng)前key節(jié)點(diǎn)則替換value,否則插入到鏈表尾部晦譬。如果f是TreeBin類型節(jié)點(diǎn)疤苹,則按照紅黑樹的方法更新或者增加節(jié)點(diǎn)

    • 若鏈表長度 > TREEIFY_THRESHOLD(默認(rèn)是8),則將鏈表轉(zhuǎn)換為紅黑樹結(jié)構(gòu), 此時(shí)還需要進(jìn)一步判斷蛔添,并不是直接轉(zhuǎn)換的(當(dāng)數(shù)組大小已經(jīng)超過64并且鏈表中的元素個(gè)數(shù)超過默認(rèn)設(shè)定(8個(gè))時(shí)痰催,將鏈表轉(zhuǎn)化為紅黑樹)

    • 調(diào)用addCount方法,ConcurrentHashMap的size + 1迎瞧,此方法還會(huì)判斷是否需要擴(kuò)容夸溶。

這里整個(gè)put操作已經(jīng)完成。

get

get操作的整個(gè)邏輯非常清楚: - 計(jì)算hash值 - 判斷table是否為空凶硅,如果為空缝裁,直接返回null - 根據(jù)hash值獲取table中的Node節(jié)點(diǎn)(tabAt(tab, (n - 1) & h)),然后根據(jù)鏈表或者樹形方式找到相對(duì)應(yīng)的節(jié)點(diǎn),返回其value值捷绑。

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 計(jì)算hash
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
        // 搜索到的節(jié)點(diǎn)key與傳入的key相同且不為null,直接返回這個(gè)節(jié)點(diǎn)
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        // 樹
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        // 鏈表韩脑,遍歷
        while ((e = e.next) != null) {
            if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

remove

size

ConcurrentHashMap的size()方法我們雖然用得不是很多,但是我們還是很有必要去了解的粹污。ConcurrentHashMap的size()方法返回的是一個(gè)不精確的值段多,因?yàn)樵谶M(jìn)行統(tǒng)計(jì)的時(shí)候有其他線程正在進(jìn)行插入和刪除操作。當(dāng)然為了這個(gè)不精確的值壮吩,ConcurrentHashMap也是操碎了心进苍。
為了更好地統(tǒng)計(jì)size,ConcurrentHashMap提供了baseCount鸭叙、counterCells兩個(gè)輔助變量和一個(gè)CounterCell輔助內(nèi)部類觉啊。

@sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

    //ConcurrentHashMap中元素個(gè)數(shù),但返回的不一定是當(dāng)前Map的真實(shí)元素個(gè)數(shù)∩虮矗基于CAS無鎖更新
    private transient volatile long baseCount;

    private transient volatile CounterCell[] counterCells;

size()方法定義如下:

public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}

內(nèi)部調(diào)用sunmCount():

final long sumCount() {
    CounterCell[] as = counterCells; CounterCell a;
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            //遍歷杠人,所有counter求和
            if ((a = as[i]) != null)
                sum += a.value;     
        }
    }
    return sum;
}

sumCount()就是迭代counterCells來統(tǒng)計(jì)sum的過程。我們知道put操作時(shí)宋下,肯定會(huì)影響size()嗡善,我們就來看看CouncurrentHashMap是如何為了這個(gè)不和諧的size()操碎了心。

在put()方法最后會(huì)調(diào)用addCount()方法杨凑,該方法主要做兩件事滤奈,一件更新baseCount的值,第二件檢測是否進(jìn)行擴(kuò)容撩满,我們只看更新baseCount部分:

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // s = b + x蜒程,完成baseCount++操作;
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            //  多線程CAS發(fā)生失敗時(shí)執(zhí)行
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }

    // 檢查是否進(jìn)行擴(kuò)容
}

x == 1伺帘,如果counterCells == null昭躺,則U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)耗美,如果并發(fā)競爭比較大可能會(huì)導(dǎo)致改過程失敗董习,如果失敗則最終會(huì)調(diào)用 fullAddCount() 方法。

其實(shí)為了提高高并發(fā)的時(shí)候 baseCount 可見性的失敗問題遗增,又避免一直重試张咳,JDK 8 引入了類 Striped64,其中 LongAdder 和 DoubleAdder 都是基于該類實(shí)現(xiàn)的帝洪,而 CounterCell 也是基于 Striped64 實(shí)現(xiàn)的。如果counterCells 脚猾!= null葱峡,且uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)也失敗了,同樣會(huì)調(diào)用 fullAddCount() 方法龙助,最后調(diào)用 sumCount() 計(jì)算 s砰奕。

其實(shí)在1.8中,它不推薦 size() 方法,而是推崇 mappingCount() 方法军援,該方法的定義和 size() 方法基本一致:

public long mappingCount() {
    long n = sumCount();
    return (n < 0L) ? 0L : n; // ignore transient negative values
}

擴(kuò)容

什么時(shí)候擴(kuò)容

  1. 當(dāng)前容量超過閾值

  2. 當(dāng)鏈表中元素個(gè)數(shù)超過默認(rèn)設(shè)定(8個(gè))仅淑,當(dāng)數(shù)組的大小還未超過64的時(shí)候,此時(shí)進(jìn)行數(shù)組的擴(kuò)容胸哥,如果超過則將鏈表轉(zhuǎn)化成紅黑樹

  3. 其他線程在擴(kuò)容時(shí)幫助擴(kuò)容

擴(kuò)容相關(guān)的屬性

采用多線程擴(kuò)容涯竟。整個(gè)擴(kuò)容過程,通過CAS設(shè)置sizeCtl烘嘱,transferIndex等變量協(xié)調(diào)多個(gè)線程進(jìn)行并發(fā)擴(kuò)容昆禽。

nextTable

擴(kuò)容期間,將table數(shù)組中的元素 遷移到 nextTable蝇庭。

sizeCtl屬性

多線程之間,以volatile的方式讀取sizeCtl屬性捡硅,來判斷ConcurrentHashMap當(dāng)前所處的狀態(tài)哮内。通過cas設(shè)置sizeCtl屬性,告知其他線程ConcurrentHashMap的狀態(tài)變更壮韭。

不同狀態(tài)北发,sizeCtl所代表的含義也有所不同。

未初始化:

sizeCtl=0:表示沒有指定初始容量喷屋。

sizeCtl>0:表示初始容量琳拨。

初始化中:

sizeCtl=-1,標(biāo)記作用,告知其他線程屯曹,正在初始化

正常狀態(tài):

sizeCtl=0.75n ,擴(kuò)容閾值

擴(kuò)容中:

sizeCtl < 0 : 表示有其他線程正在執(zhí)行擴(kuò)容

sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此時(shí)只有一個(gè)線程在執(zhí)行擴(kuò)容

transferIndex屬性

private transient volatile int transferIndex;


 /**
  擴(kuò)容線程每次最少要遷移16個(gè)hash桶
 */
private static final int MIN_TRANSFER_STRIDE = 16;

擴(kuò)容索引狱庇,表示已經(jīng)分配給擴(kuò)容線程的table數(shù)組索引位置。主要用來協(xié)調(diào)多個(gè)線程恶耽,并發(fā)安全地獲取遷移任務(wù)(hash桶)密任。

ForwardingNode節(jié)點(diǎn)

標(biāo)記作用,表示其他線程正在擴(kuò)容偷俭,并且此節(jié)點(diǎn)已經(jīng)擴(kuò)容完畢

關(guān)聯(lián)了nextTable,擴(kuò)容期間可以通過find方法浪讳,訪問已經(jīng)遷移到了nextTable中的數(shù)據(jù)

具體實(shí)現(xiàn)

  1. 在擴(kuò)容之前,transferIndex 在數(shù)組的最右邊 涌萤。此時(shí)有一個(gè)線程發(fā)現(xiàn)已經(jīng)到達(dá)擴(kuò)容閾值淹遵,準(zhǔn)備開始擴(kuò)容。
image

2 擴(kuò)容線程负溪,在遷移數(shù)據(jù)之前透揣,首先要將transferIndex左移(以cas的方式修改 transferIndex=transferIndex-stride(要遷移hash桶的個(gè)數(shù))),獲取遷移任務(wù)笙以。每個(gè)擴(kuò)容線程都會(huì)通過for循環(huán)+CAS的方式設(shè)置transferIndex淌实,因此可以確保多線程擴(kuò)容的并發(fā)安全。

image

換個(gè)角度,我們可以將待遷移的table數(shù)組拆祈,看成一個(gè)任務(wù)隊(duì)列恨闪,transferIndex看成任務(wù)隊(duì)列的頭指針。而擴(kuò)容線程放坏,就是這個(gè)隊(duì)列的消費(fèi)者咙咽。擴(kuò)容線程通過CAS設(shè)置transferIndex索引的過程,就是消費(fèi)者從任務(wù)隊(duì)列中獲取任務(wù)的過程淤年。為了性能考慮钧敞,我們當(dāng)然不會(huì)每次只獲取一個(gè)任務(wù)(hash桶),因此ConcurrentHashMap規(guī)定麸粮,每次至少要獲取16個(gè)遷移任務(wù)(遷移16個(gè)hash桶溉苛,MIN_TRANSFER_STRIDE = 16)

cas設(shè)置transferIndex的源碼如下:

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    //計(jì)算每次遷移的node個(gè)數(shù)
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; // 確保每次遷移的node個(gè)數(shù)不少于16個(gè)
    ...
    for (int i = 0, bound = 0;;) {
        ...
        //cas無鎖算法設(shè)置 transferIndex = transferIndex - stride
        if (U.compareAndSwapInt
                     (this, TRANSFERINDEX, nextIndex,
                      nextBound = (nextIndex > stride ?
                                   nextIndex - stride : 0))) {
              ...
              ...
        }
        ...//省略遷移邏輯
    }
}

過程分析

  1. 線程執(zhí)行put操作,發(fā)現(xiàn)容量已經(jīng)達(dá)到擴(kuò)容閾值弄诲,需要進(jìn)行擴(kuò)容操作愚战,此時(shí)transferindex=tab.length=32
image
  1. 擴(kuò)容線程A 以cas的方式修改transferindex=31-16=16 ,然后按照降序遷移table[31]--table[16]這個(gè)區(qū)間的hash桶
image
  1. 遷移hash桶時(shí),會(huì)將桶內(nèi)的鏈表或者紅黑樹齐遵,按照一定算法寂玲,拆分成2份,將其插入nextTable[i]和nextTable[i+n](n是table數(shù)組的長度)梗摇。 遷移完畢的hash桶,會(huì)被設(shè)置成ForwardingNode節(jié)點(diǎn)拓哟,以此告知訪問此桶的其他線程,此節(jié)點(diǎn)已經(jīng)遷移完畢伶授。
image
  1. 此時(shí)線程2訪問到了ForwardingNode節(jié)點(diǎn)断序,如果線程2執(zhí)行的put或remove等寫操作,那么就會(huì)先幫其擴(kuò)容谎砾。如果線程2執(zhí)行的是get等讀方法逢倍,則會(huì)調(diào)用ForwardingNode的find方法,去nextTable里面查找相關(guān)元素景图。
image
  1. 線程2加入擴(kuò)容操作
image
  1. 如果準(zhǔn)備加入擴(kuò)容的線程较雕,發(fā)現(xiàn)以下情況,放棄擴(kuò)容挚币,直接返回亮蒋。

    • 發(fā)現(xiàn)transferIndex=0,即所有node均已分配

    • 發(fā)現(xiàn)擴(kuò)容線程已經(jīng)達(dá)到最大擴(kuò)容線程數(shù)

    image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市妆毕,隨后出現(xiàn)的幾起案子慎玖,更是在濱河造成了極大的恐慌,老刑警劉巖笛粘,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趁怔,死亡現(xiàn)場離奇詭異湿硝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)润努,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門关斜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铺浇,你說我怎么就攤上這事痢畜。” “怎么了鳍侣?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵丁稀,是天一觀的道長。 經(jīng)常有香客問我倚聚,道長线衫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任秉沼,我火速辦了婚禮桶雀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唬复。我一直安慰自己,他們只是感情好全肮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布敞咧。 她就那樣靜靜地躺著,像睡著了一般辜腺。 火紅的嫁衣襯著肌膚如雪休建。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天评疗,我揣著相機(jī)與錄音测砂,去河邊找鬼。 笑死百匆,一個(gè)胖子當(dāng)著我的面吹牛砌些,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播加匈,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼存璃,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了雕拼?” 一聲冷哼從身側(cè)響起纵东,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啥寇,沒想到半個(gè)月后偎球,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洒扎,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年衰絮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袍冷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岂傲,死狀恐怖难裆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镊掖,我是刑警寧澤乃戈,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站亩进,受9級(jí)特大地震影響症虑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜归薛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一谍憔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧主籍,春花似錦习贫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幸海,卻和暖如春祟身,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背物独。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工袜硫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挡篓。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓婉陷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞻凤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子憨攒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容