ConcurrentHashMap1.8源码分析

1. 查询get方法

1.1 tab位置如何定位

1.1.1 tableSizeFor方法:

在初始化时将tab长度n限制为2的N次方,图1(右)以n=41为例展示tableSizeFor方法的执行过程。

tableSizeFor方法
private static final int tableSizeFor(int c) {
      int n = c - 1;
      n |= n >>> 1;
      n |= n >>> 2;
      n |= n >>> 4;
      n |= n >>> 8;
      n |= n >>> 16;
      return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  }
1.1.2 定位 hash&(n-1):

为了hash值均匀分布。

n为2的N次方,n-1则是一个二进制低位全为1的数字,在做‘与’操作时,可以减小tab中索引位置冲突概率,比如:

n=16时,n-1=15, 15的二进制为00001111,15&3(00000011)=3、15&1(00000001)=1

如果n=14时,n-1=13, 13的二进制为00001101,13&3(00000011)=1、13&1(00000001)=1,1和3就会有冲突落在一个tab槽中,同理5、7 也会分别落在同一个槽中,这样hash冲突概率就会增大。

1.2 get方法(图1 左)

图1

img

1.2.1 扩容时查询

扩容时,旧tab的node转为forwarddingNode, hash=-1;入时,synchronized保证线程安全;查询时判断node的hash值为-1,则走ForwardingNode的find方法查询,走的是新的tab查询。fwd节点是在transfer方法中替换的。

ForwardingNode类代码
static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null, null);//MOVED常量-1
            this.nextTable = tab;
        }

        Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;
                for (;;) {
                    int eh; K ek;
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;//e节点还在扩容中,继续讲tab复制到下一层tab,跳回outer执行
                            continue outer;
                        }
                        else
                            return e.find(h, k);//e为普通链接节点,去查询链表中
                    }
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }
    }

2. putVal方法

2.1 put方法流程见图2(上)

2.1.1 注意点
  1. tab数组在第一次put时初始化;
  2. 在操作节点时,CAS+synchronized锁头节点避免线程安全问题;
  3. key存在时替换value值,不存在时新增节点并且触发addCount方法;
  4. 链表数达到阈值(链表数量和tab长度两个维度限制)触发树化操作。
2.1.2 扩容时修改

触发helpTransfer方法帮助扩容,完成之后返回循环继续put节点。

图2

img

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 头节点为null时不加锁,只有一步操作用CAS和volatile即可保证线程安全
            }
            else if ((fh = f.hash) == MOVED)//扩容中,帮助扩容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {//synchronized锁保证线程安全
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            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;
    }

2.2 addCount方法流程见图2(下)

2.2.1 计数方式

ConcurrentHashMap计数时使用BASECOUNT+countCells,分步计数。有效提高多线程扩容时的效率。

2.2.2 sizeCtl参数

初始化时-1、扩容时(高16位扩容戳+(低16位线程数+1))、扩容完成后为下一次扩容阈值

addCount方法代码
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)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||// BASECOUNT增加数量失败后,随机选一个CounterCell数组中的元素增加数量
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                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);// 扩容中,将SIZECTL加1表示扩容线程+1
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);// 首次扩容,将SIZECTL=扩容时间戳<<RESIZE_STAMP_SHIFT+2(扩容线程数+1)
                s = sumCount();
            }
        }
    }

3. transfer方法

3.1 注意点

  1. 扩容时,多个线程分段迁移数据,使用i、bound参数分段。
  2. 迁移数据时,使用synchronized加锁头节点避免多线程问题
  3. 高位链、低位链方法完成快速迁移(见图3 右)
  4. 红黑树节点也是一个单向链表,迁移时同样使用高低链形式,迁移完需要判断红黑树节点是否需要链化。
图3(高低链示例图,当n=16时,示例图中写的“hash&7”是错误的,应该是“hash&15”)

img

transfer方法代码
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)
            stride = MIN_TRANSFER_STRIDE; // subdivide range stride是数据迁移时分段长度,根据cpu数量查询,最小为16
        if (nextTab == null) {            // initiating nextTab为空时初始化
            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 = n; //transferIndex初始值为tab长度
        }
        int nextn = nextTab.length;
        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))) {//nextBound赋值,n=32为例:nextIndex=32、stride=16 =》第一次:nextBound=16、i=31;第二次nextBound=0、i=15。所以每个线程以16个长度为一段操作。
                    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);//全部扩容完成,sizeCtl设置为新容量的0.75倍
                    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);//设置原tab节点位置为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;
                        }
                    }
                }
            }
        }
    }

4. 使用场景(相比HashMap)

  1. value是volatile修饰,改动时及时查询
  2. putVal时判断tab[i]是否等于null,配合volatile防止覆盖
  3. 防止并发多次扩容
  4. putIfAbsence方法,保证不覆盖
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值