前言

ConcurrentHashMap 博大精深,从他的 50 多个内部类就能看出来,似乎 JDK 的并发精髓都在里面了。但他依然拥有体验良好的 API 给我们使用,程序员根本感觉不到他内部的复杂。但,他内部的每一个方法都复杂无比,就连 size 方法,都挺复杂的。

今天就一起来看看这个 size 方法。

size 方法

代码如下:

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

最大返回 int 最大值,但是这个 Map 的长度是有可能超过 int 最大值的,所以 JDK 8 增了 mappingCount 方法。代码如下:

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

相比较 size 方法,mappingCount 方法的返回值是 long 类型。所以不必限制最大值必须是 Integer.MAX_VALUE。而 JDK 推荐使用这个方法。但这个返回值依然不一定绝对准确。

从这两个方法中可以看出,sumCount 方法是核心。

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。

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

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

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

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

而这个 CounterCell 类又是上面鬼呢?

// 一种用于分配计数的填充单元。改编自LongAdder和Striped64。请查看他们的内部文档进行解释。
@sun.misc.Contended
static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}

使用了 @sun.misc.Contended 标记的类,内部一个 volatile 变量。注释说,改编自LongAdder和Striped64,关于这两个类,请看 Java8 Striped64 和 LongAdder

而关于这个注解,有必要解释一下。这个注解标识着这个类防止需要防止 "伪共享".

说说伪共享。引用 一下别人的说法:

避免伪共享(false sharing)。

先引用个伪共享的解释:

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,

一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,

如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

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

JDK 8 版本之前没有这个注解,Doug Lea 使用拼接来解决这个问题,把缓存行加满,让缓存之间的修改互不影响。

在我的机器上测试,加和不加这个注解的性能差距达到了 5 倍。

总结

好了,关于 Size 方法就简单介绍到这里。总结一下:

JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值(size 方法是接口定义的,不能修改)。

在没有并发的情况下,使用一个 baseCount volatile 变量就足够了,当并发的时候,CAS 修改 baseCount 失败后,就会使用 CounterCell 类了,会创建一个这个对象,通常对象的 volatile value 属性是 1。在计算 size 的时候,会将 baseCount 和 CounterCell 数组中的元素的 value 累加,得到总的大小,但这个数字仍旧可能是不准确的。

还有一个需要注意的地方就是,这个 CounterCell 类使用了 @sun.misc.Contended 注解标识,这个注解是防止伪共享的。是 1.8 新增的。使用时,需要加上 -XX:-RestrictContended 参数。

并发编程 —— ConcurrentHashMap size 方法原理分析的更多相关文章

  1. 并发编程——ConcurrentHashMap#transfer() 扩容逐行分析

    前言 ConcurrentHashMap 是并发中的重中之重,也是最常用的数据结果,之前的文章中,我们介绍了 putVal 方法.并发编程之 ConcurrentHashMap(JDK 1.8) pu ...

  2. 并发编程——ConcurrentHashMap#addCount() 分析

    前言 ConcurrentHashMap 精华代码很多,前面分析了 helpTransfer 和 transfer 和 putVal 方法,今天来分析一下 addCount 方法,该方法会在 putV ...

  3. 深入理解并发编程之----synchronized实现原理

    版权声明:本文为博主原创文章,请尊重原创,未经博主允许禁止转载,保留追究权 https://blog.csdn.net/javazejian/article/details/72828483 [版权申 ...

  4. Java并发(四):并发集合ConcurrentHashMap的源码分析

    之前介绍了Java并发的基础知识和使用案例分析,接下来我们正式地进入Java并发的源码分析阶段,本文作为源码分析地开篇,源码参考JDK1.8 OverView: JDK1.8源码中的注释提到:Conc ...

  5. 并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的Ha ...

  6. 并发编程(二):分析Boost对 互斥量和条件变量的封装及实现生产者消费者问题

    请阅读上篇文章<并发编程实战: POSIX 使用互斥量和条件变量实现生产者/消费者问题>.当然不阅读亦不影响本篇文章的阅读. Boost的互斥量,条件变量做了很好的封装,因此比" ...

  7. 8.并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的Ha ...

  8. C语言C++编程学习:排序原理分析

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  9. 多线程高并发编程(3) -- ReentrantLock源码分析AQS

    背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...

随机推荐

  1. unigui验证微信服务器的有效性

    UNIGUI验证微信服务器的有效性: //////////////////////////////////////////// //UniGUIServerModuleHTTPCommand //公众 ...

  2. 操作Checkbox标签

    在前端开发中,少不了对Checkbox的操作. 常用的的方法有2个:.is()和.prop()方法.前者是判断 checkbox的状态,选不是未选.而后者为checkbox设置一个值,可以设置chec ...

  3. Android sharedUserId 和系统权限

    sharedUserId 给不同的应用使用同一个 sharedUserId 可以运行在这几个应用间互相访问数据(数据库,SharedPreferences,文件). sharedUserId 一旦使用 ...

  4. Linux启动流程与模块管理(15)

    系统的启动其实是一项非常复杂的过程,因为内核得要检测硬件并加载适当的驱动程序,接下来则必须要调用程序来准备好系统运行的环境,以让用户能够顺利的操作整台主机系统,如果你能够理解系统启动的原理,那么将有助 ...

  5. AC1000纪念

  6. 预处理命令使用详解----#if、#endif、#undef、#ifdef、#else、#elif

    预处理命令 在接触#if.#undef这类预处理指令前,大部分都都接触过#define.#include等预处理命令,通俗来讲预处理命令的作用就是在编译和链接之前,对源文件进行一些文本方面的操作,比如 ...

  7. lua 源码阅读顺序

    https://www.reddit.com/comments/63hth/ask_reddit_which_oss_codebases_out_there_are_so/c02pxbp Online ...

  8. [JavaScript] 跳出循环方法总结

    1.forEach() 方法对数组的每个元素执行一次提供的函数.但是没有办法中止或者跳出 forEach 循环,除了抛出一个异常,该方法没有返回值,return/return false/return ...

  9. WebRTC开发基础(WebRTC入门系列2:RTCPeerConnection)

    RTCPeerConnection的作用是在浏览器之间建立数据的“点对点”(peer to peer)通信. 使用WebRTC的编解码器和协议做了大量的工作,方便了开发者,使实时通信成为可能,甚至在不 ...

  10. 远程连接阿里云服务器出现"远程桌面,身份验证错误:要求的函数不受支持"解决办法

    ---恢复内容开始--- 更新:win10专业版用户可以看之前的的直接来,但家庭版用户用下面的好像并不能完美解决,献上在网上找到的一个终极解决办法 windows+R打开运行  输入regedit打开 ...