高并发下的计数器实现方式: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

相关文章

基于Redisson实现分布式系统下的接口限流

《基于Redisson实现分布式系统下的接口限流》在高并发场景下,接口限流是保障系统稳定性的重要手段,本文将介绍利用Redisson结合Redis实现分布式环境下的接口限流,具有一定的参考价值,感兴趣... 目录分布式限流的核心挑战基于 Redisson 的分布式限流设计思路实现步骤引入依赖定义限流注解实现

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H

基于Python实现进阶版PDF合并/拆分工具

《基于Python实现进阶版PDF合并/拆分工具》在数字化时代,PDF文件已成为日常工作和学习中不可或缺的一部分,本文将详细介绍一款简单易用的PDF工具,帮助用户轻松完成PDF文件的合并与拆分操作... 目录工具概述环境准备界面说明合并PDF文件拆分PDF文件高级技巧常见问题完整源代码总结在数字化时代,PD

Linux之UDP和TCP报头管理方式

《Linux之UDP和TCP报头管理方式》文章系统讲解了传输层协议UDP与TCP的核心区别:UDP无连接、不可靠,适合实时传输(如视频),通过端口号标识应用;TCP有连接、可靠,通过确认应答、序号、窗... 目录一、关于端口号1.1 端口号的理解1.2 端口号范围的划分1.3 认识知名端口号1.4 一个进程

Python实现Word转PDF全攻略(从入门到实战)

《Python实现Word转PDF全攻略(从入门到实战)》在数字化办公场景中,Word文档的跨平台兼容性始终是个难题,而PDF格式凭借所见即所得的特性,已成为文档分发和归档的标准格式,下面小编就来和大... 目录一、为什么需要python处理Word转PDF?二、主流转换方案对比三、五套实战方案详解方案1:

SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南

《SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南》本文将基于开源项目springboot-easyexcel-batch进行解析与扩展,手把手教大家如何在SpringBo... 目录项目结构概览核心依赖百万级导出实战场景核心代码效果百万级导入实战场景监听器和Service(核心

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

基于Python Playwright进行前端性能测试的脚本实现

《基于PythonPlaywright进行前端性能测试的脚本实现》在当今Web应用开发中,性能优化是提升用户体验的关键因素之一,本文将介绍如何使用Playwright构建一个自动化性能测试工具,希望... 目录引言工具概述整体架构核心实现解析1. 浏览器初始化2. 性能数据收集3. 资源分析4. 关键性能指

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1