ConcurrentHashmap中的size()方法简单解释

本文解析了JDK1.8中ConcurrentHashMap的size()和mappingCount()方法的实现原理,介绍了CounterCell的作用及其实现细节,解释了为何需要遍历counterCells数组来获取容器的大小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文所有的源码都是基于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 的不同实现

 

转载于:https://www.cnblogs.com/loren-Yang/p/7466111.html

### Java 1.7 中 `ConcurrentHashMap` 的文档使用 #### 类概述 `ConcurrentHashMap` 是一种线程安全的哈希表实现,在多线程环境中提供了高效的并发访问能力。相比于传统的同步集合类如 `Hashtable` 或者通过外部加锁的方式保护 `HashMap`,`ConcurrentHashMap` 提供了更好的性能和伸缩性。 #### 主要特性 - **高并发度**:允许多个读操作以及一定数量的同时写入操作而不需要完全锁定整个映射。 - **分段锁机制**:内部采用分段(Segment)来存储数据,默认情况下有16个分段,每个分段相当于一个小的哈希表并有自己的锁对象[^1]。 #### 基本方法 以下是 `ConcurrentHashMap` 支持的一些常用的方法: - `put(K key, V value)`:如果指定键不存在,则将其关联到给定值;否则更新该键对应的旧值。 - `get(Object key)` :返回指定键所映射的值;如果没有找到则返回null。 - `remove(Object key)` : 移除由key指定条目。 - `containsKey(Object key)` : 判断是否存在某个特定的键。 - `size()` : 获取当前容器内元素的数量。 - `isEmpty()` : 测试此映射是否为空。 #### 使用示例 下面是一个简单的例子展示了如何创建和操作 `ConcurrentHashMap` 实例: ```java import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapExample { public static void main(String[] args) throws InterruptedException { // 创建一个新的 ConcurrentHashMAp 对象 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); // 添加一些初始项 map.put("one", 1); map.put("two", 2); System.out.println(map.get("one")); // 输出: 1 // 并发修改map中的值 Thread t1 = new Thread(() -> { for(int i=0;i<5;i++){ map.put(Integer.toString(i),i*2); } }); Thread t2 = new Thread(() -> { for(int j=0;j<5;j++){ map.remove(Integer.toString(j)); } }); t1.start(); t2.start(); t1.join(); t2.join(); // 打印最终的结果集 System.out.println(map); } } ``` 这段代码首先初始化了一个 `ConcurrentHashMap` ,接着启动两个线程分别向其中添加和删除项目。由于 `ConcurrentHashMap` 内置的支持,即使是在多线程环境下也能正常工作而不必担心竞态条件等问题的发生[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值