聊一聊concurrenthashmap的size方法原理

目录
  • concurrenthashmap的size方法原理
    • 下面具体说一下这个size方法
  • concurrenthashmap的size的思考
    • 我们首先会想到以下两种方法

concurrenthashmap的size方法原理

同上,这也是同一个面试的时候别人问的,我只是记得看过,在concurrenthashmap中会统计多次,当时就说会统计两次进行比较,人家接着问为啥。。。我傻了一下,这不是明摆着两次统计的中间有新的变化了,会导致统计不准确吗?当时也不知道说啥好,以为他有新的点,就说不知道。面试时很多问题其实冷静下来想一下,可以更进一步的,有时候其实也是怕他更进一步后下面的挖坑挖大了。

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

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

众所周知,concurrenthashmap有很多歌segments,首先遍历segments将每个segment的count加起来作为整个concurrenthashMap的size。如果没有并发的情况下这自然就可以了,但这是多线程的,如果前脚统计完后脚有变化了,这就不准确了,源码中引入了,modCount和两次比较来实现size的确认。具体过程是:

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

将每个segemnt的count加起来作为总数,期间把每个segment的modCount加起来sum作为结果是否被修改的判断依据。

这里需要提一下modCount,这个是当segment有任何操作都会进行一次增量操作,代表的是对Segment中元素的数量造成影响的操作的次数,这个值只增不减!!!!只增不减很重要,这样就不会出现一个segment+1,导致modcount+1,而另一个segment-1,即modcount-1 ,从而在统计所有的时候modcount没有变化。

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

每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次,如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历了。

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

那就如上所说,要重新启用全部segment加锁的方式来进行count的获取和统计了,这样在此期间每个segement都被锁住,无法进行其他操作,统计出的count自然很准确。

而之所以之所以要先不加锁进行判断,道理很明显,就是不希望因为size操作获取这么多锁,因为获取锁不光占用资源,也会影响其他线程对ConcurrentHash的使用,影响并发情况下程序执行的效率。使用锁要谨慎!

原理大概就是这样的,具体的代码可以去看源码,而且源码1.7和1.8有差别。。。有空再贴出来比较比较吧。

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。

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

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

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

(0)

相关推荐

  • Java concurrency集合之ConcurrentHashMap_动力节点Java学院整理

    ConcurrentHashMap介绍 ConcurrentHashMap是线程安全的哈希表.HashMap, Hashtable, ConcurrentHashMap之间的关联如下: HashMap是非线程安全的哈希表,常用于单线程程序中. Hashtable是线程安全的哈希表,它是通过synchronized来保证线程安全的:即,多线程通过同一个"对象的同步锁"来实现并发控制.Hashtable在线程竞争激烈时,效率比较低(此时建议使用ConcurrentHashMap)!因为当一

  • JDK1.8中的ConcurrentHashMap使用及场景分析

    ConcurrentHashMap 的初步使用及场景: ConcurrentHashMap 是 J.U.C 包里面提供的一个线程安全并且高效的 HashMap,所以ConcurrentHashMap 在并发编程的场景中使用的频率比较高,那么我们就从ConcurrentHashMap 的使用上以及源码层面来分析 ConcurrentHashMap 到底是如何实现安全性的 api 使用: ConcurrentHashMap 是 Map 的派生类,所以 api 基本和 Hashmap 是类似,主要就是

  • java无锁hashmap原理与实现详解

    java多线程环境中应用HashMap,主要有以下几种选择:使用线程安全的java.util.Hashtable作为替代​使用java.util.Collections.synchronizedMap方法,将已有的HashMap对象包装为线程安全的.使用java.util.concurrent.ConcurrentHashMap类作为替代,它具有非常好的性能.而以上几种方法在实现的具体细节上,都或多或少地用到了互斥锁.互斥锁会造成线程阻塞,降低运行效率,并有可能产生死锁.优先级翻转等一系列问题.

  • 聊一聊concurrenthashmap的size方法原理

    目录 concurrenthashmap的size方法原理 下面具体说一下这个size方法 concurrenthashmap的size的思考 我们首先会想到以下两种方法 concurrenthashmap的size方法原理 同上,这也是同一个面试的时候别人问的,我只是记得看过,在concurrenthashmap中会统计多次,当时就说会统计两次进行比较,人家接着问为啥...我傻了一下,这不是明摆着两次统计的中间有新的变化了,会导致统计不准确吗?当时也不知道说啥好,以为他有新的点,就说不知道.面

  • 详解C#扩展方法原理及其使用

    1.写在前面 今天群里一个小伙伴问了这样一个问题,扩展方法与实例方法的执行顺序是什么样子的,谁先谁后(这个问题会在文章结尾回答).所以写了这边文章,力图从原理角度解释扩展方法及其使用. 以下为主要内容: 什么是扩展方法 扩展方法原理及自定义扩展方法 扩展方法的使用及其注意事项 2.什么是扩展方法 一般而言,扩展方法为现有类型添加新的方法(从面向对象的角度来说,是为现有对象添加新的行为)而无需修改原有类型,这是一种无侵入而且非常安全的方式.扩展方法是静态的,它的使用和其他实例方法几乎没有什么区别.

  • jQuery中size()方法用法实例

    本文实例讲述了jQuery中size()方法用法.分享给大家供大家参考.具体分析如下: 此方法可以返回jQuery对象集合中元素的数目. size()方法的返回值与jQuery对象的length属性一致. 语法结构: 复制代码 代码如下: $(selector).size() 实例代码: 复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta na

  • python @propert装饰器使用方法原理解析

    这篇文章主要介绍了python @propert装饰器使用方法原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 首先,@propert的作用是把类中的方法『变成』了属性,方便通过实例访问.propert可以有两种用法:可以把一个方法变成只读属性:可以对一些属性进行过滤. 想象这样一个场景,在实例化一个类之后,需要对类的一个属性进行赋值,这时候是没有对属性属性被赋予的值进行判断的,如果属性被赋予了一个不合适的值,那么代码在后面执行的时候就会

  • Java binarysearch方法原理详解

    这篇文章主要介绍了Java binarysearch方法原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 首先 数组排序需要import java.util.Arrays类 binarysearch有两个用法,一个是在整个数组里搜索,一个是在指定范围搜索,其实都是差不多的,可以将前者视为后者的特殊情况. 用法1 binarySearch(Object[], Object key) Object是目标数组,key是目标值,要求目标数组必须要

  • python线程join方法原理解析

    这篇文章主要介绍了python线程join方法原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 几个事实 1 python 默认参数创建线程后,不管主线程是否执行完毕,都会等待子线程执行完毕才一起退出,有无join结果一样 2 如果创建线程,并且设置了daemon为true,即thread.setDaemon(True), 则主线程执行完毕后自动退出,不会等待子线程的执行结果.而且随着主线程退出,子线程也消亡. 3 join方法的作用是阻

  • Python基于read(size)方法读取超大文件

    pyhon读取文件很方便,但是,如果文件很大,而且还是一行文件,那就蛋疼了. 不过还好有read(size)方法,这个方法就是每次读取size大小的数据到内存中 下面来个示例 def readlines(f, separator): ''' 读取大文件方法 :param f: 文件句柄 :param separator: 每一行的分隔符 :return: ''' buf = '' while True: while separator in buf: position = buf.index(s

  • Python面向对象程序设计之私有变量,私有方法原理与用法分析

    本文实例讲述了Python面向对象程序设计之私有变量,私有方法原理与用法.分享给大家供大家参考,具体如下: 私有变量,私有方法: python的私有化是为了规划私有属性,避免非相关的访问[假如!我有老婆,你不能直接知道我老婆是谁,你只有问我才会知道,即我的私有属性只有我知道] 在python中定义私有变量和私有方法只需要在变量名或函数名前加上 "__"两个下划线 在类内部的方法中使用时  self.__变量名或函数名. 实际上,如果真要访问私有变量和私有方法,也是可以访问的,因为实际上

  • Python面向对象程序设计之静态方法、类方法、属性方法原理与用法分析

    本文实例讲述了Python面向对象程序设计之静态方法.类方法.属性方法原理与用法.分享给大家供大家参考,具体如下: 前言:认为方法与函数同意义,由于这里使用method,所以称为方法 静态方法: 使用@staticmethod来定义静态方法. 静态方法:类和实例都可以调用的方法,实际上跟类没什么关系了,对于这种不太相关的就使用静态方法[用实例和类调用没有区别] class Dog(object): def __init__(self,name): self.name=name def talk(

  • Java面试题之HashMap 的 hash 方法原理是什么

    Warning:这是<Java 程序员进阶之路>专栏的第 55 篇. 回来后小二找到了我,于是我就写下了这篇文章丢给他,并严厉地告诉他:再搞不懂就别来找我.听到这句话,心头一阵酸,小二绷不住差点要哭

随机推荐