ConcurrentHashMap的size()是线程安全的吗?为什么?

1、无论JDK1.7和JDK1.8中的ConcurrentHashMap的size()方法都是线程安全的

2、为什么那

JDK1.7的ConCurrentHashMap的size方法,先不加锁获取一次size长度,然后再获取一次,最多获取三次,比较前后两次获取的值,如果两个获取的值相同则不存在竞争的操作,线程安全,直接返回值就行;但是如果前后两次获取的值不同,那么会将每个Segment都加上锁,然后计算ConcurrentHashMap的size的值(Segment加锁保证线程安全)

JDK1.8的ConCurrentHashMap的size方法源码如下:

    public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }

这个方法最大返回的是int最大值,但是ConcurrentHashMap的返回值有可能超过int的最大值;

JDK1.8中增加了mappingCount()方法,这个方法的返回值是long,所以JDK1.8推荐使用mappingCount获取map中数据的数量。

   public long mappingCount() {
        long n = sumCount();
        return (n < 0L) ? 0L : n; // ignore transient negative values
    }

无论size()方法还是mappingCount()方法,核心方法都是sumCount()

源码如下:

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;
    }

在sumCount()方法中可以看出当counterCells为空,直接返回baseCount,当counterCells不为空时返回的是遍历累计并加上baseCount

baseCount源码:

   /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    private transient volatile long baseCount;

baseCount是一个volatile变量,在put()方法执行时怎么使用的baseCount的,在put()方法中的最后调用addCount()方法,addCount()源码如下:

private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            //1、CAS自增操作
            !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 ||
                //2、baseCount 失败进入countercells CAS
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                //3、都失败进入fullAddCount自旋,直到成功
                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);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

首先对baseCount做CAS自增操作

如果并发导致baseCount的CAS失败,则使用counterCells进行CAS,如果counterCells也失败

则会进入fullAddCount()方法,fullAddCount()方法进入自旋,直到成功为止

那么CounterCell到底是什么那?源码如下:

    /**
     * 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; }
    }

是一个使用@sun.misc.Contended标记的类,内部是一个volatile修饰的变量。

@sun.misc.Contended注解是防止伪共享的

什么是伪共享:

缓存系统中是以缓存行(cache line)为单位存储的,缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

所以伪共享对性能危害极大

JDK1.8版本之前没有这个注解,JDK1.8版本之后使用拼接的方式解决这一问题,把缓存行加满,让缓存直接的修改互不影响。

总结:

1、ConcurrentHashMap的size()方法是线程安全的

2、在JDK1.7版本Segment加锁的方法保证线程安全,JDK1.8版本之后,使用的是CAS和Volatile或synchronized来保证线程安全

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值