高并发下的计数器实现方式:AtomicLong、LongAdder、LongAccumulator

图片

一、前言

计数器是并发编程中非常常见的一个需求,例如统计网站的访问量、计算某个操作的执行次数等等。在高并发场景下,如何实现一个线程安全的计数器是一个比较有挑战性的问题。本文将介绍几种常用的计数器实现方式,包括AtomicLong、LongAdder和LongAccumulator,并深入讲解其中的CAS操作。

二、计数器

计数器是一种非常基础的数据结构,用于记录某个事件发生的次数。在并发编程中,由于多个线程可能同时对计数器进行修改,因此需要保证计数器的线程安全性。

三、AtomicLong

AtomicLong是Java中的一个原子类,主要作用是对长整形进行原子操作,保证并发情况下数据的安全性。它实现了一系列线程安全的方法,包括初始化为特定值和以原子方式设置当前值等。

AtomicLong的核心机制是通过CAS(Compare and Swap)操作来确保并发安全性。CAS是一种无锁算法,其核心思想是:如果内存中的值V符合预期值A,则将内存中值修改为B,否则不进行任何操作。整个过程是原子的,不会出现线程安全问题。在高并发环境下,当大量线程同时竞争更新同一个原子变量时,只有一个线程的CAS会成功,其他线程会不断尝试直到成功,这就可能造成大量线程竞争失败后,通过无限循环不断尝试自旋尝试CAS操作,白白浪费了CPU资源。

图片

图里可以看出在高并发情况下,当有大量线程同时去更新一个变量,任意一个时间点只有一个线程能够成功,绝大部分的线程在尝试更新失败后,会通过自旋的方式再次进行尝试,这样严重占用了 CPU 的时间片,进而导致系统性能问题。

多线程并发下AtomicLong实现计数器demo:


import java.util.concurrent.atomic.AtomicLong;public class AtomicLongCounter {
private AtomicLong counter = new AtomicLong(0);public void increment() {
long oldValue, newValue;
do {oldValue = counter.get();newValue = oldValue + 1;} while (!counter.compareAndSet(oldValue, newValue));}public long getCount() {
return counter.get();}public static void main(String[] args) throws InterruptedException {AtomicLongCounter counter = new AtomicLongCounter();
int threadCount = 10;Thread[] threads = new Thread[threadCount];for (int i = 0; i < threadCount; i++) {threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {counter.increment();}});threads[i].start();}for (int i = 0; i < threadCount; i++) {threads[i].join();}System.out.println("计数器的值:" + counter.getCount());}
}

四、LongAdder

LongAdder是Java 8新增的一个类,主要用于解决高并发下的计数问题。与AtomicLong不同,LongAdder内部采用了分段锁技术,将一个大的计数空间分成若干个小的空间进行累加操作。每个小空间都有一个独立的锁,当多个线程同时对不同的小空间进行累加操作时,它们可以并行执行,从而提高了并发性能。

图片

如图所示,LongAdder 设计思想上,采用分段的方式降低并发冲突的概率。通过维护一个基准值 base 和 Cell 数组。

多线程并发下LongAdder实现计数器demo:


import java.util.concurrent.atomic.LongAdder;public class LongAdderCounter {
private final LongAdder longAdder = new LongAdder();public void increment() {longAdder.increment();}public long getCount() {
return longAdder.sum();}public static void main(String[] args) throws InterruptedException {LongAdderCounter counter = new LongAdderCounter();
int threadCount = 10;Thread[] threads = new Thread[threadCount];for (int i = 0; i < threadCount; i++) {threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {counter.increment();}});threads[i].start();}for (int i = 0; i < threadCount; i++) {threads[i].join();}System.out.println("计数器的值:" + counter.getCount());}
}

五、LongAccumulator

LongAccumulator是Java 8新增的一个类,用于实现自定义的累加操作。它提供了一种简单而灵活的方式来实现复杂的累加逻辑。LongAccumulator内部维护了一个累加结果和一个标识位,当调用accumulate方法时,会根据标识位的值来决定是否直接返回结果还是进入累加逻辑。这种方式可以有效地避免重复计算和线程竞争问题。


import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.util.LongAccumulator;public class LongAccumulatorCounter {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("LongAccumulatorCounter").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);LongAccumulator longAccumulator = sc.longAccumulator();JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5), 2);rdd.foreachPartition(partition -> {
for (int value : partition) {longAccumulator.add(value);}});System.out.println("累加器的值:" + longAccumulator.value());sc.stop();}
}

六、CAS(Compare and Swap)

CAS 全称:compare and swap,比较并交换。CAS操作是上述三种计数器实现方式的核心机制之一。它通过比较内存中的值和预期值是否相等来判断是否需要进行更新操作。如果相等,则将内存中的值修改为新值;否则不做任何操作。整个过程是原子的,不会出现线程安全问题。但是需要注意的是,在高并发场景下,当多个线程同时竞争同一个原子变量时,可能会出现“ABA”问题。即当一个线程读取了内存中的值A之后,另一个线程将其修改为B再修改为A,此时第一个线程再次读取该变量时会发现它的值仍然是A而不是B。为了解决这个问题,可以使用版本号等方式来解决“ABA”问题,使用Java提供的AtomicStampedReference 类。

七、总结

阿里巴巴推荐使用 LongAdder, 原因主要有以下几点:

高并发性能:LongAdder 采用分段锁的策略,可以避免 AtomicLong 中的竞争问题,提高并发性能。在分布式系统中,高并发性能是非常重要的。

可扩展性:LongAdder 支持可扩展性,可以通过增加更多的段来提高性能。这对于需要处理大量请求的分布式系统来说是非常有利的。

代码简单易懂:虽然LongAdder 的代码相对复杂一些,但是相对于 AtomicLong 来说更容易理解和维护。这对于开发人员来说是非常重要的。

更好的适用场景:阿里巴巴推荐使用 LongAdder 主要是因为在分布式系统中需要一个高性能、高可用的计数器实现。而 LongAdder 正好符合这个需求。

图片

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

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

相关文章

3.4 在开发中使用设计模式

现在&#xff0c;我们应该对设计模式的本质以及它们的组织方式有了初步的认识&#xff0c;并且能够理解ROPES过程在整体设计中的作用。通过之前章节对“体系结构”及其五个视图的探讨&#xff0c;我们打下了坚实的基础。初步了解了UML的基本构建模块后&#xff0c;我们现在可以…

gem5学习(11):将缓存添加到配置脚本中——Adding cache to the configuration script

目录 一、Creating cache objects 1、Classic caches and Ruby 二、Cache 1、导入SimObject(s) 2、创建L1Cache 3、创建L1Cache子类 4、创建L2Cache 5、L1Cache添加连接函数 6、为L1ICache和L1DCache添加连接函数 7、为L2Cache添加内存侧和CPU侧的连接函数 完整代码…

适用于安防 音响 车载等产品中中的音频接口选型分析

在人工智能兴起之后&#xff0c;安防市场就成为了其全球最大的市场&#xff0c;也是成功落地的最主要场景之一。对于安防应用而言&#xff0c;智慧摄像头、智慧交通、智慧城市等概念的不断涌现&#xff0c;对于芯片产业催生出海量需求。今天&#xff0c;我将为大家梳理GLOBALCH…

自动化测试框架pytest系列之强大的fixture功能,为什么fixture强大?一文拆解它的功能参数。(三)

自动化测试框架pytest系列之基础概念介绍(一)-CSDN博客 自动化测试框架pytest系列之21个命令行参数介绍(二)-CSDN博客 接上两篇文章继续 &#xff1a; 3.3 pytest支持的初始化和清除函数 学过unittest的都知道 &#xff0c;unittest有四个函数 &#xff0c;分别是 &#xff…

PPT插件-大珩助手-快速构建自己的图形

绘图板-快速构建自己的图形 通过手绘的方式&#xff0c;快速构建自己的想法和创意&#xff0c;通过在PPT中插入绘图&#xff0c;植入背景透明的绘图&#xff0c;点击画笔可切换橡皮擦&#xff0c;可以清空画板重新绘制。 素材库-存储图形 通过素材库存储自己的图形 图形调整…

操作系统期末考复盘

简答题4题*5 20分计算题2题*5 10分综合应用2题*10 20分程序填空1题10 10分 1、简答题&#xff08;8抽4&#xff09; 1、在计算机系统上配置OS的目标是什么&#xff1f;作用主要表现在哪个方面&#xff1f; 在计算机系统上配置OS&#xff0c;主要目标是实现:方便性、有…

如何把123转换成字符串的123

在许多编程语言中&#xff0c;将数字123转换为字符串的"123"是非常直接的。以下是几种常见编程语言的示例&#xff1a; Python num 123 str_num str(num) print(str_num) # 输出: 123 JavaScript let num 123; let str_num num.toString(); console…

three.js 学习笔记(学习中1.10更新) |

文章目录 three.js 学习笔记基础概念透视相机 第一个three.js应用threejs画布尺寸和布局canvas画布宽高度动态变化 坐标辅助器 THREE.AxesHelper实现动画效果requestAnimationFrame时间相关属性和方法 THREE.Clock类 相机控件 轨道控制器OrbitControls 灯光点光源点光源辅助观察…

m1 + swoole(hyperf) + yasd + phpstorm 安装和debug

参考文档 Mac M1安装报错 checking for boost... configure: error: lib boost not found. Try: install boost library Issue #89 swoole/yasd GitHub 1.安装boost库 brew install boostbrew link boost 2.下载yasd git clone https://github.com/swoole/yasd.git 3.编…

轻量化神奇!看3D模型格式转换工具HOOPS Exchange如何轻松实现减面操作?

现在很多CAD模型都比较复杂&#xff0c;有时候为了一些特殊用途&#xff08;轻量化显示、布尔运算、CAE网格剖分等&#xff09;&#xff0c;需要到对原始模型进行减面操作。在HOOPS Exchange中&#xff0c;就提供了对模型进行减面操作支持&#xff0c;以下内容就是HOOPS Exchan…

亚信安慧AntDB数据库容灾复制原理

AntDB数据库作为通信运营商领域的杰出的数据服务提供者&#xff0c;一直以来都十分重视数据安全问题&#xff0c;不断通过技术进步、方案创新等方式提升数据容灾能力。在信息化的时代&#xff0c;数据已经成为了重要的资源&#xff0c;对于企业来说&#xff0c;如何存储和管理这…

docker微服务案例

文章目录 建立简单的springboot项目(boot3)boot2建立通过dockerfile发布微服务部署到docker容器编写Dockerfile打包成镜像运行镜像微服务 建立简单的springboot项目(boot3) 1.建立module 2. 改pom <?xml version"1.0" encoding"UTF-8"?> <…

python爬虫实战(7)--获取it某家热榜

1. 需要的类库 import requests from bs4 import BeautifulSoup import pandas as pd2. 请求榜单 def fetch_ranking_data():url "https://m.xxx.com/rankm/" #某家response requests.get(url)if response.status_code 200:return response.contentelse:print(f…

计算机网络期末复习(二)

物理层 解决&#xff1a;如何在连接各种计算机的传输媒体上传输比特流&#xff0c;而不是具体的传输媒体。 传输媒体&#xff1a;比如双绞线、同轴电缆、光纤等等。 主要任务&#xff1a;确定于传输媒体接口有关的一些特性。 传输媒体 导向形 非导向形 调制解调器 数字信号 和…

CSGO服务器搭建细节

在搭建CSGO服务器之前&#xff0c;我们首先需要确保电脑上已经安装好了所需的软件环境。这里我们需要安装SteamCMD和HLDSUpdateTool工具&#xff0c;并在安装之后进行相关的配置&#xff0c;才能顺利地跑起服务器。同时&#xff0c;在准备环境的同时&#xff0c;我们还要考虑服…

贝锐蒲公英云智慧组网解读:实现工业设备远程调试、异地PLC互联

这个时候&#xff0c;使用异地组网是非常有效的解决方案。在12月28日贝锐官方的直播中&#xff0c;请到了贝锐蒲公英的技术研发经理&#xff0c;为大家分享了贝锐蒲公英云智慧组网解决方案&#xff0c;以及蒲公英二层组网相关的技术和应用。 搜索“贝锐”官方视频号&#xff0c…

XCTF:再见李华[WriteUP]

从题目中下载到图片 md5&#xff1a;1a4fb3fb5ee12307 十六位的哈希数&#xff0c;但没任何意义(只有迷惑作用) 扔进binwalk中分析有没有隐藏的文件 发现有个ZIP文件 使用foremost尝试提取 foremost mail2LiHua.jpg 提取成功 但是这个zip文件开了密码 里面的key应该就是…

【图解面试】JS系列 - 如何回答数据类型相关问题(上)

1. JS中的数据类型有哪些&#xff0c;他们的区别是什么&#xff1f; 知识点大纲 语言组织&#xff08;示例&#xff09; 要点&#xff1a;数量 → 种类 → 区别 JS中的数据类型主要有 8 种&#xff0c;分为两大类 基础数据类型 和 引用数据类型 基础数据类型中主要有 Numbe…

11Spring IoC注解式开发(下)(负责注入的注解/)

1负责注入的注解 负责注入的注解&#xff0c;常见的包括四个&#xff1a; ValueAutowiredQualifierResource 1.1 Value 当属性的类型是简单类型时&#xff0c;可以使用Value注解进行注入。Value注解可以出现在属性上、setter方法上、以及构造方法的形参上, 方便起见,一般直…

【电商API接口】jd.item_search按关键字搜索京东商品返回值和参数说明

item_search-按关键字搜索商品 [查看演示] API测试工具 注册开通 jd.item_search 公共参数 请求地址: 申请调用KEY测试 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08…