ConcurrentHashMap的size的思考

ConcurrentHashMap是通过分段锁来控制整个Map的安全性和并发性,那么ConcurrentHashMap在求size的时候是如何兼顾到性能以及安全性的呢?

我们首先会想到以下两种方法:

1.

获取所有的Segment锁。这个方法是可行的,但是这会导致并发性能变差,因为你获取了所有的锁,那么别的线程将无法对该HashMap执行任何操作。

2.

逐个地获取Segment。这种方法也有问题,有可能在后面获取下一个Segment里面的元素的个数的时候,上面一个Segment里面元素的个数已经很可能改变了,因此最后累加到最后,有可能数据是错误的。

 

那么ConcurrentHashMap采用的是什么措施呢。源码如下所示:

java1.7以前的源码:

由于在累加count的操作的过程中之前累加过的count发生变化的几率非常小,所以ConcurrentHashMap先尝试2次不锁住Segment的方式来统计每个Segment的大小,如果在统计的过程中Segment的count发生了变化,这时候再加锁统计Segment的count。

ConcurrentHashMap的size的思考_第1张图片

java1.7以及1,7以后的源码:

取size的核心是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;
    }

核心逻辑:当 counterCells 不是 null,就遍历元素,并和 baseCount 累加。

查看两个属性 : baseCount 和 counterCells。

先看 baseCount,

private transient volatile long baseCount;

baseCount是一个 volatile 的变量,在 addCount 方法中会使用它,而 addCount 方法在 put 结束后会调用。在 addCount 方法中,会对这个变量做 CAS 加法。

  private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSetLong(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.compareAndSetLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }

但是如果并发导致 CAS 失败了,怎么办呢?使用 counterCells。

如果上面 CAS 失败了,在 fullAddCount 方法中,会继续死循环操作,直到成功。

 

最后,再来看一下counterCells​​​​​​​这个类。

    @jdk.internal.vm.annotation.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

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


 

你可能感兴趣的:(ConcurrentHashMap的size的思考)