concurrenthashmap sizectl,concurrenthashmap默认大小

  concurrenthashmap sizectl,concurrenthashmap默认大小

  00-1010 concurrent hashmap的大小方法原理。先说并发hashmap的size方法的思路。我们首先会想到以下两种方法。

  00-1010同上。这也是在同一次面试中被别人问到的。只记得看过,在concurrenthashmap里算了很多次。当时我说我再数两遍比较一下,人家接着问为什么。我太傻了。这两个统计中间不是明显有新变化,会导致统计不准吗?我当时不知道该说什么。我以为他有新东西,就说不知道。面试的时候,很多问题其实静下心来想一想,就可以更进一步了。有时候,你害怕他走得更远之后,下面的坑会被挖得更大。

  

目录

代码就不贴了。只说原理。

 

  众所周知,concurrenthashmap中有很多歌曲片段。首先,遍历这些段,并将每个段的计数加起来作为整个concurrenthashMap的大小。如果没有并发性,这是正常的,但它是多线程的。如果统计后前脚变了,是不准确的。源代码中引入了modCount和两次比较来实现大小的确认。具体流程是:

  1.进行第一遍遍历segments数组

  将每一段的计数加起来作为总数,将每一段的模计数加起来求和,作为判断结果是否被修改的依据。

  这里有必要提一下modCount。当段中有任何操作时,这是一个增量操作,它表示影响段中元素数量的操作数量。这个值只增不减!重要的是只增加或减少,这样就不会有一个导致modcount 1的segment 1,和另一个segment-1,即modcount-1,这样当所有的统计数据都可用时modcount不会改变。

  2.size操作就是遍历了两次所有的Segments

  记录每个分段的modCount值,然后比较两个mod count。如果相同,说明期间没有写操作,返回原来的遍历结果。如果它们不相同,该过程将再次重复。如果它们不相同,则需要逐个锁定和遍历所有段。

  3.如果经判断发现两次统计出的modCount并不一致

  那么,如上所述,需要重新启用锁定所有segements的方式来获取和计数。这样,在此期间,每个段都被锁定,不能进行其他操作。算出来的数自然是准确的。

  之所以要先判断不加锁,原因很明显,就是不想因为大小操作而获取那么多锁,因为获取锁不仅占用资源,还会影响其他线程对ConcurrentHash的使用,以及并发条件下程序执行的效率。小心使用锁!

  大概就是这个原理吧。具体代码可以看源码,源码1.7和1.8有区别。有时间再贴上来对比一下。

  

concurrenthashmap的size方法原理

ConcurrentHashMap通过分段锁控制整个Map的安全性和并发性。那么在寻求大小时,ConcurrentHashMap如何平衡性能和安全性呢?

 

  

下面具体说一下这个size方法

1.获取所有的Segment锁。

 

  这种方法是可行的,但是会导致较差的并发性能,因为如果您获取了所有的锁,其他线程将无法在HashMap上执行任何操作。

  2.逐个地获取Segment。

  这种方法也有问题。有可能后面得到下一段的元素个数时,上一段的元素个数很可能已经发生了变化,所以最后累加到最后时数据可能是错误的。

  那么ConcurrentHashMap采取什么措施呢?源代码如下:

  java1.7以前的源码:

  因为之前累计的计数在累计计数的过程中发生变化的概率非常小,所以ConcurrentHashMap首先尝试通过不锁定两次来统计每个段的大小。如果在计数的过程中该段的计数发生变化,则将其锁定以计数该段的计数。

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

  size的核心是sumCount函数。

  最终long sum count(){ count cell[]as=count cells

  ; 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个字节。

  当多线程修改互相独立的变量时, 如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

  以上为个人经验,希望能给大家一个参考,也希望大家多多支持盛行IT。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: