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

本文主要是介绍高并发下的计数器实现方式: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 正好符合这个需求。

图片

这篇关于高并发下的计数器实现方式:AtomicLong、LongAdder、LongAccumulator的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/575420

相关文章

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

C#实现添加/替换/提取或删除Excel中的图片

《C#实现添加/替换/提取或删除Excel中的图片》在Excel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更加美观,下面我们来看看如何在C#中实现添加/替换/提取或删除E... 在Excandroidel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更

C#实现系统信息监控与获取功能

《C#实现系统信息监控与获取功能》在C#开发的众多应用场景中,获取系统信息以及监控用户操作有着广泛的用途,比如在系统性能优化工具中,需要实时读取CPU、GPU资源信息,本文将详细介绍如何使用C#来实现... 目录前言一、C# 监控键盘1. 原理与实现思路2. 代码实现二、读取 CPU、GPU 资源信息1.