认真研究ConcurrentHashMap中的元素统计策略

news/2024/5/18 19:13:06/文章来源:https://blog.csdn.net/J080624/article/details/126604676

这里我们想研究的是jdk1.8中ConcurrentHashMap的addCount(long x, int check)方法。如下所示在put方法的最后会触发addCount(long x, int check)方法进行元素个数的统计。

在这里插入图片描述

我们再回顾一下另一个参数binCount :

  • 在操作链表的分支if (fh >= 0)中 用于统计put前链表长度
  • if (f instanceof TreeBin) 分支中看到, binCount=2 , 该值被直接赋值常量 2

触发addCount的场景

  • putVal(K key, V value, boolean onlyIfAbsent)方法中最后会触发addCount(1L, binCount);
  • replaceNode方法中会触发addCount(-1L, -1)
  • clear()方法中触发addCount(delta, -1);;
  • compute或者computeIfAbsent或者computeIfPresent方法中触发 addCount((long)delta, binCount)

【1】addCount

添加到计数,若table太小且尚未调整大小,则触发transfer。如果当前正在扩容,则尝试帮助进行扩容调整。在transfer后重新检查占用情况,以查看是否需要另一次调整,因为resizings 是滞后的添加。

Rechecks occupancy after a transfer to see if another resize is already needed because resizings are lagging additions.

// x 表示需要 add 的数
// check < 0 ,不需要检查resize, check <= 1 only check if uncontended
private final void addCount(long x, int check) {CounterCell[] as; long b, s;// counterCells 默认为null,如果as为null且没有成功更新BASECOUNT就进入if// 如果as不为null,直接进入ifif ((as = counterCells) != null ||!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {CounterCell a; long v; int m;//记录是否存在竞争,true表示不存在竞争boolean uncontended = true;// m = as.length - 1//a = as[ThreadLocalRandom.getProbe() & m]// 如果a 不为null,那么更新a.value = a.value+xif (as == null || (m = as.length - 1) < 0 ||(a = as[ThreadLocalRandom.getProbe() & m]) == null ||// 这里还会将CAS结果赋予uncontended !(uncontended =U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {fullAddCount(x, uncontended);return;}// check <= 1 直接返回if (check <= 1)return;// 求和    baseCount+ΣCounterCell.values = 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) {// 这几个条件也是有bug的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);// 需要注意的是,在扩容后,这里又触发了一次    sumCounts = sumCount();}}
}

从上面代码可以看到,统计的最终还是依赖于fullAddCount(x, uncontended)sumCount()

① 出现的几个变量&常量

//基本计数器值,主要在没有争用时使用,但在表初始化竞争期间也用作后备。通过CAS更新。
private transient volatile long baseCount;/*** Spinlock (locked via CAS) used when resizing and/or creating CounterCells.*///旋转锁(locked via CAS),当扩容或者创建CounterCells时使用
private transient volatile int cellsBusy;/*** Table of counter cells. When non-null, size is a power of 2.*/// 存放CounterCell的数组,不为null时,其是2的N次幂
private transient volatile CounterCell[] counterCells;// 对应变量baseCount
private static final long BASECOUNT=U.objectFieldOffset(k.getDeclaredField("baseCount"));
// 对应变量cellsBusy
private static final long CELLSBUSY=U.objectFieldOffset(k.getDeclaredField("cellsBusy"));
// 对应变量 CounterCell.value
private static final long CELLVALUE=U.objectFieldOffset(ck.getDeclaredField("value"));//数组的最大长度 tab.leght
private static final int MAXIMUM_CAPACITY = 1 << 30;// 扩容戳移动位数
private static int RESIZE_STAMP_BITS = 16;/*** The maximum number of threads that can help resize.* Must fit in 32 - RESIZE_STAMP_BITS bits.*/// 最大扩容线程数 65535
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;/*** The bit shift for recording size stamp in sizeCtl.*/// 扩容戳移位数  = 16
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

CounterCell是什么呢?用于分配计数的填充单元格。改编自LongAdder和Striped64。有关解释,请参阅其内部文档。所以,我们还得研究下LongAdder和Striped64。

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

② 触发fullAddCount的分支

第一个if

if ((as = counterCells) != null ||!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) 

进入if 方法体的场景:

  • counterCells不为null
  • counterCells为null,但是不能CAS更新BASECOUNT=BASECOUNT+x

第二个if

if (as == null || (m = as.length - 1) < 0 ||(a = as[ThreadLocalRandom.getProbe() & m]) == null ||!(uncontended =U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)))

进入if 方法体的场景(as=counterCells):

  • ① as 为null;
  • ② as 不为null,但是(m = as.length - 1) < 0,这里其实先给 m 进行了赋值,然后判断。如果判断为真,那么说明counterCells是一个空数组。
  • ③ ①②都不满足,a = as[ThreadLocalRandom.getProbe() & m]) == null。这里获取了一个CounterCell 赋予了a。
  • ④ 不能更新CELLVALUE 为 a.value+x。

③ 统计所有CounterCell的value和

也就是baseCount+ΣCounterCell.value

final long sumCount() {CounterCell[] as = counterCells; CounterCell a;// 将sum更新为baseCountlong sum = baseCount;if (as != null) {// 遍历每一个CounterCell 获取value进行累加for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}// 返回sumreturn sum;
}

【2】LongAdder

LongAdder 继承自Striped64,也是java.util.concurrent.atomic包下的一个原子类。想要搞明白fullAddCount,必须搞懂LongAdder。

一个或多个变量均持有 long sum(初始零)。当线程并发更新(比如add方法)时,变量集可能会动态增长以减少竞争。方法sum(或等价的longValue)返回持有sum的变量的合计值。

当多个线程更新用于收集统计信息而不是细粒度同步控制的公共和时,此类通常优于AtomicLong。在低更新竞争下,这两个类具有相似的特性。但在高竞争情况下,此类的预期吞吐量显著更高,但代价是更高的空间消耗。

LongAdder可以和ConcurrentHashMap一起使用以维持一个 可伸缩的 frequency map(a form of histogram or multiset)。例如,要将一个计数添加到一个ConcurrentHashMap<String,LongAdder> freqs,如果尚未初始化,那么可以使用 freqs.computeIfAbsent(k -> new LongAdder()).increment();

这个类继承自Number,但是没有定义方法诸如equals、hashCode和compareTo。因为实例常常会发生改变,所以作为集合的键不是那么有用。

如下是其add方法,可以看到ConcurrentHashMap的addCount方法是参考了这个add方法。

public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||(a = as[getProbe() & m]) == null ||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x, null, uncontended);}
}

如下所示是其 sum 方法,与ConcurrentHashMap中sumCount()方法可以说简直一致。

public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}

【3】fullAddCount

// See LongAdder version for explanationprivate final void fullAddCount(long x, boolean wasUncontended) {int h;if ((h = ThreadLocalRandom.getProbe()) == 0) {ThreadLocalRandom.localInit();      // force initializationh = ThreadLocalRandom.getProbe();wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {CounterCell[] as; CounterCell a; int n; long v;if ((as = counterCells) != null && (n = as.length) > 0) {if ((a = as[(n - 1) & h]) == null) {if (cellsBusy == 0) {            // Try to attach new CellCounterCell r = new CounterCell(x); // Optimistic createif (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean created = false;try {               // Recheck under lockCounterCell[] rs; int m, j;if ((rs = counterCells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {rs[j] = r;created = true;}} finally {cellsBusy = 0;}if (created)break;continue;           // Slot is now non-empty}}collide = false;}else if (!wasUncontended)       // CAS already known to failwasUncontended = true;      // Continue after rehashelse if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))break;else if (counterCells != as || n >= NCPU)collide = false;            // At max size or staleelse if (!collide)collide = true;else if (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {try {if (counterCells == as) {// Expand table unless staleCounterCell[] rs = new CounterCell[n << 1];for (int i = 0; i < n; ++i)rs[i] = as[i];counterCells = rs;}} finally {cellsBusy = 0;}collide = false;continue;                   // Retry with expanded table}h = ThreadLocalRandom.advanceProbe(h);}else if (cellsBusy == 0 && counterCells == as &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init = false;try {                           // Initialize tableif (counterCells == as) {CounterCell[] rs = new CounterCell[2];rs[h & 1] = new CounterCell(x);counterCells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))break;                          // Fall back on using base}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_3388.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TinyRenderer学习笔记--Lesson 3、4

Lesson 3 zbuffer 无论怎样&#xff0c;生活中的显示器基本上都是平面&#xff0c;是一个2D的场景&#xff0c;而我们的模型却是3D的&#xff0c;是有深度的&#xff0c;实际上我们看见的都只是离我们的眼睛最近的那一个平面&#xff0c;一个不透明的3D物体的内部和背面是我们…

河北稳控科技使用标准信号检测 VM振弦采集模块测量精度

河北稳控科技使用标准信号检测 VM振弦采集模块测量精度(一) (1)电源1.1VDD 引脚电源必须使用 LDO 稳压或者低纹波线性电源, LDO 推荐使用 AM1117_3.3V 芯片,测试时发现 SPX 生产的 LDO会造成非常严重的干扰(其它品牌应该也会有类似的问题)。1.2VSEN 引脚电源单通道模块…

阿里、滴滴、华为等一线互联网分布式消息中间件:RocketMQ核心笔记

本篇介绍了RocketMQ的基本使用方法及其各个组件的基本原理&#xff0c;讲解原理时&#xff0c;都是采用先整体架构后详细分解的方式。详细分解时不会深入源码逐段讲&#xff0c;而是从代码结构出发梳理整个运行过程。 这份RocketMQ分布式消息中间件—核心原理与最佳实践的完整…

Android Studio应用基础,手把手教你从入门到精通(小白学习)总结2 之 常用界面布局和ListView

总结1链接&#xff1a; (156条消息) Android Studio应用基础&#xff0c;手把手教你从入门到精通&#xff08;小白学习&#xff09;总结1_好喜欢吃红柚子的博客-CSDN博客 学习视频链接&#xff1a; &#xff08;学完必会&#xff09;Android studio基础&#xff0c;从入门到…

尚好房 07_前端房源展示

尚好房&#xff1a;前端房源展示 一、分页显示房源列表 1、效果 2、项目搭建 2.1 创建项目 在web项目中创建子工程web-front 2.2 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0&…

stm32学习(二)|ADC电压采集DMA

利用ADC通道采集外部传感器数值,ADC通道选择依据实际查询芯片手册可得,相关配置利用Cubemx完成。 ADC参数配置首先选择需要使用的ADC通道,并设置对应的引脚ADC_IN0X.ADC参数设置(Paremeter setting)Mode : Independent mode,只使用一个ADC通道 Clock Prescaler,Resolut…

OpenGL 反色

目录 一.OpenGL 反色 1.IOS Object-C 版本2.Windows OpenGL ES 版本3.Windows OpenGL 版本 二.OpenGL 反色 GLSL Shader三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >&…

Windows OpenGL ES 图像反色

目录 一.OpenGL ES 图像反色 1.原始图片2.效果演示 二.OpenGL ES 图像反色源码下载三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 特效 零基础 OpenGL E…

责任链模式

1、责任链模式是什么 行为模式&#xff0c;一个对象产生的消息会被另外的对象处理。对象发出消息后&#xff0c;不管被哪种、多少个其他对象收到和处理消息。【客户端和handler解耦】 2、为什么使用 如果不使用责任链&#xff0c;则client要知道有多少个handler、什么情况调…

2.IP子网划分

IP子网划分地址分类网络位与主机位一个网段可以容纳多少IPIP地址&#xff1a;互联网中计算机的‘身份证号’&#xff0c;唯一标识一台网络设备的身份ID NAT技术&#xff1a;网络地址转换&#xff0c;节约公网IP 例: IP地址 192.168.1.1 192.168.1 …

电商数仓项目中各层的表

ODS operation Data store 操作数据存储 DWD Data Warehouse detail 细节数据层, DIM Dimension---------------范围&#xff0c;维度 DWS Data Warehouse Summary 数据库汇总 ADS Application Data Service 应用数据服务层 【电商数仓每一层的表】 【ODS层】 operation Data s…

Spring之AOP思想

目录 什么是AOP ​​​为什么用AOP Spring AOP 应该怎么学习呢 AOP下的一些核心概念&#xff08;SpringAOP并没有实现所有的概念&#xff09; 基于概念的使用Spring的AOP 一个使用的实例 关于切点的匹配 通知的种类 使用注解的方式来实现功能​编辑 AOP框架背后的核心 …

TypeScript 小结

TypeScript 是什么&#xff1f; TypeScript 是由微软开发的一种自由和开源的编程语言。它是 JavaScript 的一个超集&#xff0c;本质上是在 JavaScript 的基础上添加了可选的静态类型和基于类的面向对象编程。 TypeScript 和 JavaScript 的区别&#xff1f; TypeScript 的安装…

Netty(10)协议设计与解析(IdleStateHandler:空闲检测器、心跳)

为什么需要协议&#xff1f; TCP/IP 中消息传输基于流的方式&#xff0c;没有边界。 协议的目的就是划定消息的边界&#xff0c;制定通信双方要共同遵守的通信规则 协议举例 redis 协议 客户端代码 import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf…

Webmin -- Sheduled Commands

at作业(Webmin称之为预定命令)类似Scheduled Cron Jobs&#xff0c;但不是按调度重复地执行&#xff0c;而是仅在指定的日期和时间运行一次。不同于Cron作业&#xff0c;可以配置它们在指定目录而不是在用户的家目录中执行。预定的命令也跟踪在创建时设置的环境变量&#xff0c…

C++ 哈希桶模拟实现(补充)

目录 定义基本的存储结构 Insert()和Find() Erase() 如何控制哈希冲突&#xff1f; Insert()中添加扩容操作 其他问题的解决 UnorderedMap.h和UnorderedSet.h 迭代器实现与UnorderedMap.h和UnorderedSet.h的封装 定义基本的存储结构 #pragma once #include<iostream&…

Rethinking the Inception Architecture for Computer Vision--Christian Szegedy

Christian Szegedy, Vincent Vanhoucke, Sergey Ioffe, Jonathon Shlens, & Zbigniew Wojna (2016). Rethinking the Inception Architecture for Computer Vision computer vision and pattern recognition. 0、摘要1、引入2、通用设计原则2.1 避免表征瓶颈2.2 特征数据越…

安卓毕业设计成品基于Uniapp+SSM实现的智能课堂管理APP在线学习网

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Uniapp项目介绍 资料获取 Uniapp项目介绍 计算机毕业设计安卓App毕设项目之ssm智能课堂管理APP-IT实战课堂_哔哩哔哩_bilibili计算机毕业设计安卓App毕设项目之ssm智能课堂管理APP-IT实…

常用的基本命令(必掌握)

目录 常用的基本命令&#xff08;必掌握&#xff09; 目录管理 基本属性 修改文件属性 文件内容查看 拓展&#xff1a;Linux 链接概念 常用的基本命令&#xff08;必掌握&#xff09; 目录管理 绝对路径和相对路径 我们知道Linux的目录结构为树状结构&#xff0c;最顶级…

有序的Map集合

我们通常使用的Map集合是HashMap&#xff0c;在大多数情况下HashMap可以满足我们的要求&#xff0c;但是HashMap有一个缺点&#xff1a;HashMap****是无序的&#xff0c;即其迭代顺序与其key或value的大小无关。而在某些情况下&#xff0c;如果我们需要Map集合里的元素有序&…