Loading

ConcurrentHashMap扩容

并发map的存储结构是怎样的?

和普通的HashMap一样,都是数组+链表+红黑树结构
Node结构,有key,有value,指向下一个位置的指针,还有hash字段

Node的Hash值一般是大于0的,为什么?
因为负数的hash值有其他的意义,在扩容的时候,迁移完毕的桶要放入一个forwardingNode节点(MOVED状态)-1,还有一种情况,红黑树的treebin节点,默认是-2

sizeCtl字段如果是-1表示?
表示这个表正在做初始化(initTable)和HashMap一样,只不过并发map要保证在多线程下这个table只能创建一次。当多线程方式去initTable的时候,就会用CAS方式来修改sizeCtl的值。sizeCtl初始值为0,期待更新后的值是-1
CAS失败的线程,会进行一个自选检查,检查当前table是否有没有被初始化出来,检查的时候,会让线程短暂的释放它所占用的CPU,让当前线程去重新竞争CPU资源。吧CPU让给更饥饿的线程去竞争

sizeCtl大于0
表示下次触发扩容的阈值

sizeCtl负数,不是-1,表示这个散列表正在处于扩容状态,高16位表示扩容标识戳,低16位表示参与扩容的线程数+1。

扩容标识戳的计算方法:
每个线程计算的戳必须一致,能标记出是从同一个小表到同一个大表的扩容。
eg:从16扩容到32,每个线程计算出来的扩容唯一标识戳都是同一个值
image
image
image
image
image
image
image
image

image
image
image
image
image
image
image
image
image

寻址算法??
增强散裂行

并发map统计数据数量LongAdder

LongAdder VS AtomicLong
因为At采用CAS
image

image
image
image
image
image
image
image
image
image
image

image
image
image
image

image
image
image
image
image
image

image
image
image
image
image
image
image

image
image
image
image
image
image
image
image
标识迁移进度
image

迁移完毕的桶,会用fwd来表示

image

image
image
image
image

image

image
image
image
image
image
image
image
image
image
image
image
image
image

image

image

image
image

image

image
image
image
image
image

收尾
image

image
image
image
image
image
image
image

image
image
image

image

image
image
image
image

image

image

image

image

image

image
image

image
image

image
image

image

image
image
image

image

image
红黑树保留了链表结构,如果红黑树处于写的状态,就使用红黑树维护的链表上进行查询。

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

Next字段是解决Hash冲突生成链表用的。

并发Map的负载因子0.75f不能修改,是static final类型的。
put方法

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)
            // 第一次put,进行数组初始化
                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)))
                             // 通过CAS方式进行元素插入,如果没有成功,会继续循环尝试
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
            // 表示当前Map正在扩容
                tab = helpTransfer(tab, f);
            else {
                // 会有并发问题,所以需要sync
                // 判断当前节点要加入链表还是红黑树
                V oldVal = null;
                synchronized (f) {
                    // 对链表的头节点进行加锁
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            // 是链表
                            // binCount大小决定是否树化
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    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;
                                }
                            }
                        }
                        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) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

initTable方法

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
            // 刚开始的时候sizeCtl一定为0,所以走如下else if分支
            // 放弃竞争,重新竞争
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                // 用CAS方式来对sc进行控制,只有一个线程可以将其减到-1
                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;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

addCount方法

首先对size+1,然后判断是否需要扩容,如果需要,就调用扩容方法。

各个线程先对baseCount竞争+1,只有一个竞争成功,另外的线程生成随机数并且&lengthOfCountCell - 1
得到的这个线程对应CounterCell数组中的位置,然后对这个位置的值+1,表示这个线程已经完成执行,无需再参与竞争。结合size方法来查看。(baseCount + sumOfCountCellValue) 分散竞争。

addCount方法有可能传入的check = -1, x = -1, 在remove方法里面。

private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
                // counterCells数组为空或者CAS操作没有成功
            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))) {
                      // counterCells数组为空,CAS操作CELLVALUE失败
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            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);
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

size()方法

public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }
 final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

fullAddCount方法

private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;
            if ((as = counterCells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {            // Try to attach new Cell
                    // CounterCell空的,且没有其他线程用
                    // 就New一个新的CounterCell对象
                    // CAS方式填充CounterCell
                        CounterCell r = new CounterCell(x); // Optimistic create
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            try {               // Recheck under lock
                                CounterCell[] 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;
                }
                else if (!wasUncontended)       // CAS already known to fail
                // 重新基于新的hash值来生成新的下标
                //  h = ThreadLocalRandom.advanceProbe(h);
                    wasUncontended = true;      // Continue after rehash
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                else if (counterCells != as || n >= NCPU)
                // CounterCell不能无休止的扩容 
                // 或者n不会超过CPU的核心线程数,就不进行扩容了。
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                             // collide = true 需要扩容
                             // CAS方式还没有找到一个CounterCell的空位置,就会进行扩容
                    try {
                        if (counterCells == as) {// Expand table unless stale
                        // 扩容CounterCell数组
                            CounterCell[] 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)) {
                    // counterCell此时是空的
                    // cellsBusy = 0说明没有其他线程在使用
                    // CAS上一个开关,说明已经有线程在用这个数组了
                boolean init = false;
                try {                           // Initialize table
                    if (counterCells == as) {
                        CounterCell[] rs = new CounterCell[2];
                        // 将CounterCell位置上的value+1。
                        rs[h & 1] = new CounterCell(x);
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            // 一个线程如果改不了CounterCells位置上的值,就用来CAS改baseCount的值。
                break;                          // Fall back on using base
        }
    }

扩容原理

addCount方法中如下分支

        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);
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // sc等于阈值的时候
                    // 只有一个线程可以把sc改成负数
                    // 只有进入这个if里面的transfer方法中才能创建新的table
                    transfer(tab, null);
                s = sumCount();
            }
        }

transfer方法

用于转移元素到新table

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        // 最小步长是16
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            // transferIndex是扩容之前的大小
            transferIndex = n;
        }
        // 新数组长度
        int nextn = nextTab.length;
        // 表示数组正在扩容。在
        // put方法中用到,用MOVED来标志
        // put时候,如果是MOVED,表示这个数组正在扩容。会调用和helpTransfer()方法来帮助扩容。
        // 如果一个位置转移完毕了,会在这个位置上放置一个fwd
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        // 当前线程是否需要按步长前进
        boolean advance = true;
        // 当前线程扩容的逻辑是否做完
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> 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))) {
                    // CAS来控制
                    // 当前线程要控制的区域是哪些
                    // 不会有线程冲突
                    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)) {
                    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 ((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);
                                else
                                    hn = 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;
                                    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<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;
                        }
                    }
                }
            }
        }
    }

image

参考资料

ConcurrentHashMap源码深入解析

原始:

image

扩容第一步
image

1.7扩容方法
Segment对象内部进行扩容,多个线程是多个Segment对象。支持多线程扩容。

1.8扩容方法

  1. 计算一个步长
    假设步长=2,从右往左拿两个元素依次转移
    image

在扩容完老数组以后,可能并发量比较高的时候,会继续判断新数组是否需要扩容。

image

image

image
image

image
image

image

posted @ 2021-10-06 21:07  Grey Zeng  阅读(31)  评论(0编辑  收藏  举报