JDK1.8中的ConcurrentHashMap源代码分析,为什么高效

先上总结

本文是按照JDK8的源码分析.

  1. 使用compareAndSwap
  2. 利用LongAdder原理
  3. 当冲突的元素达到一定数量时, 使用红黑树管理冲突的元素; 较少时, 仍然使用链表
  4. 扩容时, 并不是粗暴的让其它线程等待, 而是让它们参与扩容过程中, 达到加速效果

数据结构

初始状态16个槽(数组形式), 每个槽中都是相似hash(不是相同)的元素. 元素数量达到阈值时, 会以翻倍的形式进行扩容

Hash-0
Hash-1
Hash-2
Hash-3
Hash-4
Hash-5
........................
Hash-15
Hash-1
Hash-1
Hash-5
Hash-5
Hash-5
Hash-5
Hash-5
Hash-5

预备知识

compareAndSwap原理 https://blog.csdn.net/wzj_whut/article/details/86772268
cpu缓存问题 https://blog.csdn.net/wzj_whut/article/details/86774650
LongAdder原理 https://blog.csdn.net/wzj_whut/article/details/86775838
红黑树, 网上到处都是资料.

源码解读

源码我也只看懂了部分.

成员变量

 	//主要存储区域, 冲突的元素, 会链表t和树的形式挂在Node上.
    transient volatile Node<K,V>[] table;

	//扩张的时候所使用的临时表
    private transient volatile Node<K,V>[] nextTable;

	//ConcurrentHashMap又重造了轮子LongAdder, 自己去看上文中的[LongAdder原理]
    private transient volatile int cellsBusy;
    private transient volatile long baseCount;//
    private transient volatile CounterCell[] counterCells;

hash处理

也这也是重点, 否则无法理解重新分表的处理过程

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

ConcurrentHashMap对key的hash重新处理了. 为什么这么做?
因为进行分表时, 它只利用了hash值的低位, 因此将hash的高位与低位异或, 可以减少hash的冲突的可能性.
初始状态下, 表的数量为16 (即: 2 4 2^4 24), 会取hash的低4位进行分表.
假设有以下两个hash值 (spread处理之后的值)
h1 = xxxx001102
h2 = xxxx101102
它们的低4位, 都是0110, 因此, 当表的数量为16时, 这两个值会分配到同一个槽中(0110为槽6).
扩容
当map的元素数量达到阈值时, 会扩容. 首次扩容后槽的数量为32 , hash值的低5位用于分配.
设扩容前槽的数量为n.
h1仍分配到槽6
h2将分配到22. (可以写为 h1 & (n-1) + 16)

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, 因为sizeCtl会被作为一个标志位, 用于线程同步
            	//如果 sizeCtl<0, 则说明另外一个线程正在执行初始化, 本线程将等待其初始化完成
                Thread.yield(); // lost initialization race; just spin
            }else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                //将sizeCtl置为-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*0.75 = 12
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

tabAt

使用unsafe接口读取数组元素, 我猜测其中的一个原因是: 这样做比使用java中的下标操作array[i]更快.

    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

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)
            	//无锁方式初始化table, 上面已经讲过
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            	//初始化table数组中的元素. 通过compareAndSwap方式
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED){
            	//表中的数据正在往新表迁移, 本线程也尝试参与到迁移过程中, 加快迁移速度. [高明]
                tab = helpTransfer(tab, f);
            }else {
                V oldVal = null;
                synchronized (f) {
               		//正式执行put操作, 这此操作synchronized, 以确保100%线程安全.
                    if (tabAt(tab, i) == f) {
                    	//f并不是通过线程安全的方式取得的, 因此必须再判断一次
                        if (fh >= 0) { //此时,Node是普通的链表形式
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                            	//遍历表中的内容
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    //发现key已经存在了
                                    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是红黑树形式
                            Node<K,V> p;
                            binCount = 2; //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){
                    	//链表形式的table, 元素数据达到阈值之后, 转化为红黑树形式
                        treeifyBin(tab, i);
                    }
                    if (oldVal != null){
                    	//本次操作, 使用新值替换了旧值, map中元素的数量不变
                        return oldVal;
                    }
                    break;
                }
            }
        }
        //新增了一个元素, 总数+1
        addCount(1L, binCount);
        return null;
    }

helpTransfer

当前线加入到扩容过程中.

addCount

修改map的size计数, 例如put操作需要+1, remove操作需要-1. 如果是增长, 那么就有可能需要扩容, 增加更多的分区.
先看看LongAdder原理 https://blog.csdn.net/wzj_whut/article/details/86775838

    private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        //通过[LongAdder原理]修改总数, 看我的博客https://blog.csdn.net/wzj_whut/article/details/86775838
        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))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1){
            	//不需要扩容. 场景有:
            	// 1. 删除元素或替换元素
            	//  2. 元素数量较少,还在使用链表结构
                return;
            }    
            //可能需要扩容, 取出总数
            s = sumCount();
        }
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //sizeCtl为总容量的0.75
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) { //MAXIMUM_CAPACITY= 2的30次方
 
                // rs = Integer.numberOfLeadingZeros(n) | (1 << 15);
                // 因为n为2的幂, 若n变化, 那么肯定是n向左移位了, numberOfLeadingZeros(n)的值就必然变化
                // 最后将numberOfLeadingZeros(n)存放在rs的低15位上.
                // 可以这么认为: rs低15存放了n的特征值,其它的位用来做flag.
                // 
                int rs = resizeStamp(n);
                if (sc < 0) {//负数表示正在初始化,或是正在扩容中
                	//sc<0时, sc == (rs << 16) + 2)
                	//sc>>>16之后,得到rs
                	//若rs不同,说明其它线程正在执行transfer(tab, null)
                	//若rs相同,但sc == rs + 1,说明其它线程正在执行 transfer(tab, nt)
                	//若rs相同,sc != rs +1 , 但是sc == rs + ((1<<16)-1). 这个我也不懂
                	//nextTable==null, 
                    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)){
                    	//本线程加入到扩容过程中. 此时sc是一个负数.
                        transfer(tab, nt);
                    }
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2)){
                    //说明当前线程是本轮扩容任务中的头号玩家.
                    transfer(tab, null);
                }
                s = sumCount();
            }
        }
    }

transfer

扩容过程, 这是最精华的部分. 我也一知半解
参考: https://www.cnblogs.com/stateis0/p/9062086.html

   private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        //stride为每次处理的槽的数量. 
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE){
            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 = n;
        }
        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
        //transferIndex用于维护处理的进度.
        //当线程运行到此处时, 根据transferIndex领取需要执行迁移操作的槽, 然后执行迁移操作
        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))) {
                    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) { //表示是链表形式
                        	//runBit要么是n, 要么是0
                        	//为0时, 新的hash未变, 
                        	//为n时, 表明hash会出现变化
                            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) {
                        	//红黑树中的节点也应该是按照hash值的规律排列的, 我还没看.
                            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;
                        }
                    }
                }
            }
        }
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值