zoukankan      html  css  js  c++  java
  • ConcurrentHashmap中的size()方法简单解释

    本文所有的源码都是基于JDK1.8

    ConcurrentHashmap中的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;
        }

    根据JDK1.8的注解,当你想大致了解ConcurrentHashmap的容器大小时,建议使用mappingCount()方法。源码如下:

    /**
         * Returns the number of mappings. This method should be used
         * instead of {@link #size} because a ConcurrentHashMap may
         * contain more mappings than can be represented as an int. The
         * value returned is an estimate; the actual count may differ if
         * there are concurrent insertions or removals.
         *(大致的意思是:返回容器的大小。这个方法应该被用来代替size()方法,因为
         * ConcurrentHashMap的容量大小可能会大于int的最大值。
         * 返回的值是一个估计值;如果有并发插入或者删除操作,则实际的数量可能有所不同。)
         * @return the number of mappings
         * @since 1.8
         */
        public long mappingCount() {
            long n = sumCount();
            return (n < 0L) ? 0L : n; // ignore transient negative values
        }

    其实baseCount就是记录容器数量的,直接放回baseCount不就可以了吗?为什么sumCount()方法中还要遍历counterCells数组,累加对象的值呢?

    其中:counterCells是个全局的变量,表示的是CounterCell类数组。CounterCell是ConcurrentHashmap的内部类,它就是存储一个值。

    /**
         * Table of counter cells. When non-null, size is a power of 2.
         */
        private transient volatile CounterCell[] counterCells;
    /**
         * A padded cell for distributing counts.  Adapted from LongAdder
         * and Striped64.  See their internal docs for explanation.
         */
        @sun.misc.Contended static final class CounterCell {
            volatile long value;
            CounterCell(long x) { value = x; }
        }

    JDK1.8中使用一个volatile类型的变量baseCount记录元素的个数,当插入新数据put()或则删除数据remove()时,会通过addCount()方法更新baseCount:

    private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
    
        //U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 每次竟来都baseCount都加1因为x=1
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1
            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发生失败的时候执行
                fullAddCount(x, uncontended);//2
                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) {//如果小于0说明已经有线程在进行扩容操作了
                    //一下的情况说明已经有在扩容或者多线程进行了扩容,其他线程直接break不要进入扩容操作
                    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已经等于(rs << RESIZE_STAMP_SHIFT) + 2等于一个大的负数,这边加上2很巧妙,因为transfer后面对sizeCtl--操作的时候,最多只能减两次就结束
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

    1、初始化时counterCells为空,在并发量很高时,如果存在两个线程同时执行CAS修改baseCount值,则失败的线程会继续执行方法体中的逻辑,执行fullAddCount(x, uncontended)方法,这个方法其实就是初始化counterCells,并将x的值插入到counterCell类中,而x值一般也就是1,这单可以从put()方法中得知。

    final V putVal(K key, V value, boolean onlyIfAbsent) {
    ...
    //1就是要CAS 更新baseCount的值,binCount代表此链表或树的值,一般都大于0.
       addCount(1L, binCount);
        return null;
    }

    所以counterCells存储的都是value为1的CounterCell对象,而这些对象是因为在CAS更新baseCounter值时,由于高并发而导致失败,最终将值保存到CounterCell中,放到counterCells里。这也就是为什么sumCount()中需要遍历counterCells数组,sum累加CounterCell.value值了。

    参考博客:
    谈谈 ConcurrentHashMap1.7 和 1.8 的不同实现

  • 相关阅读:
    鼠标划过出现子菜单
    让dedecms(织梦)的list标签支持weight排序
    win7 64位无法安装网络打印机
    点击外部链接, 让iframe父页面也跟着显示
    C/C++指针(转)
    OO与设计模式的原则、目标 (转)
    页面添加QQ
    Windows Form 中的鼠标事件
    深入浅出C#消息
    初始化列表
  • 原文地址:https://www.cnblogs.com/loren-Yang/p/7466111.html
Copyright © 2011-2022 走看看