zoukankan      html  css  js  c++  java
  • Java集合之ConcurrentHashMap.addCount解析

      ConcurrentHashMap 精华代码很多,前面分析了 helpTransfer 和 transfer 和 putVal 方法,今天来分析一下 addCount 方法,该方法会在 putVal 方法中调用。

      一起来看看 addCount 是如何操作的。

      源码分析

    /**
     * 变更容器大小
     * 
     * @param x     添加的元素个数
     * @param check if < 0, 不检查调整, if <= 1 只有在未竞争时才检查
     */
    private final void addCount(long x, int check) {
        // 计数盒子
        CounterCell[] as; 
        long b, s;
    
        // 当计数盒子不为null 或 通过cas直接更新baseCount失败
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
    
            // 无竞争标记
            boolean uncontended = true;
    
            // 当计数盒子为null 或 计数盒子长度为0 
            // 或 随机取计数盒子中的1个元素如果为null 或 通过CAS直接更新该随机元素失败
            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)
                return;
    
            // 获取当前容器元素个数
            s = sumCount();
        }
    
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
    
            // sizeCtl 阈值(仅在数组未初始化时,等于数据容量)
            // 如果map.size() 大于 sizeCtl(达到扩容阈值需要扩容) 
            // 且table 不是空;且 table 的长度小于 1 << 30。(可以扩容)
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
    
                // 根据 length 得到一个标识
                int rs = resizeStamp(n);
    
                // 是否正在扩容
                if (sc < 0) {
                    // 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
                    // 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)
                    //(默认第一个线程设置 sc == rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减 1。
                    //     这个时候,sc 就等于 rs + 1)
                    // 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
                    // 如果 nextTable == null(结束扩容了)
                    // 如果 transferIndex <= 0 (转移状态变化了)
                    // 结束循环
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
    
                    // 如果可以帮助扩容,那么将 sc 加 1. 表示多了一个线程在帮助扩容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        // 扩容
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // 更新 sizeCtl 为负数后,开始扩容。
                    transfer(tab, null);
    
                s = sumCount();
            }
        }
    }

      CounterCell数组的作用是什么呢?ConcurrentHashMap是采用CounterCell数组来记录元素个数的,像一般的集合记录集合大小,直接定义一个size的成员变量即可,当出现改变的时候只要更新这个变量就行。为什么ConcurrentHashMap要用这种形式来处理呢? 问题还是处在并发上,ConcurrentHashMap是并发集合,如果用一个成员变量来统计元素个数的话,为了保证并发情况下共享变量的的安全,势必会需要通过加锁或者自旋来实现,如果竞争比较激烈的情况下,size的设置上会出现比较大的冲突反而影响了性能,所以在ConcurrentHashMap采用了分片的方法来记录大小,具体什么意思,我们来分析下

    public class ConcurrentHashMap<K,V> extends Abstract{
     
        ...
     
        // 标识当前cell数组是否在初始化或扩容中的CAS标志位
        private transient volatile int cellsBusy;
     
        // counterCells数组,总数值的分值分别存在每个cell中
        private transient volatile CounterCell[] counterCells;
     
        @sun.misc.Contended 
        static final class CounterCell {
         
            volatile long value;
             
            CounterCell(long x) { value = x; }
        }
     
        //看到这段代码就能够明白了,CounterCell数组的每个元素,都存储一个元素个数,而实际我们调用size方法就是通过这个循环累加来得到的
         
        //又是一个设计精华,大家可以借鉴; 有了这个前提,再会过去看addCount这个方法,就容易理解一些了
        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;
        }
    }

      总结一下

      总结下来看,addCount 方法做了 2 件事情:

    1. 对 table 的长度加一。无论是通过修改 baseCount,还是通过使用 CounterCell。当 CounterCell 被初始化了,就优先使用他,不再使用 baseCount。

    2. 检查是否需要扩容,或者是否正在扩容。如果需要扩容,就调用扩容方法,如果正在扩容,就帮助其扩容。

      有几个要点注意:

    1. 第一次调用扩容方法前,sizeCtl 的低 16 位是加 2 的,不是加一。所以 sc == rs + 1 的判断是表示是否完成任务了。因为完成扩容后,sizeCtl == rs + 1。

    2. 扩容线程最大数量是 65535,是由于低 16 位的位数限制。

    3. 这里也是可以帮助扩容的,类似 helpTransfer 方法。

      参考资料

     
  • 相关阅读:
    ubuntu 制做samba
    《Programming WPF》翻译 第4章 前言
    《Programming WPF》翻译 第4章 3.绑定到数据列表
    《Programming WPF》翻译 第4章 4.数据源
    《Programming WPF》翻译 第5章 6.触发器
    《Programming WPF》翻译 第4章 2.数据绑定
    《Programming WPF》翻译 第4章 1.不使用数据绑定
    《Programming WPF》翻译 第5章 7.控件模板
    《Programming WPF》翻译 第5章 8.我们进行到哪里了?
    《Programming WPF》翻译 第5章 5.数据模板和样式
  • 原文地址:https://www.cnblogs.com/AndroidJotting/p/14183344.html
Copyright © 2011-2022 走看看