深入解析Java并发库(JUC)中的LongAdder

简介: 深入解析Java并发库(JUC)中的LongAdder

核心概述

LongAdder是一个用于并发环境中的长整型加法操作的类,它提供了比AtomicLong更高的吞吐量。LongAdder在内部维护了一个或多个变量(取决于当前并发级别和系统环境),每个线程对其中一个变量进行操作,从而减少了线程间的竞争。当需要获取总和时,这些变量会被加在一起。


与AtomicLong相比,它通过内部维护多个Cell对象,采用分段化的方式降低线程间的并发冲突,从而提高了性能。然而,这种设计也带来了一定的内存开销。LongAdder常用于需要高并发更新的统计和

一、LongAdder的使用

下面代码展示了如何在多线程环境中使用LongAdder来统计并发任务的执行次数,并最终获取总的执行次数。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderComplexExample {

    public static void main(String[] args) throws InterruptedException {
        // 创建一个LongAdder用于统计任务执行次数
        LongAdder taskCounter = new LongAdder();

        // 创建一个线程池用于执行并发任务
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // 创建一个任务列表
        List<Runnable> tasks = new ArrayList<>();

        // 添加100个任务到任务列表
        for (int i = 0; i < 100; i++) {
            final int taskId = i;
            tasks.add(() -> {
                // 模拟任务执行时间
                try {
                    Thread.sleep((long) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                // 任务执行完毕,增加计数器
                taskCounter.increment();
                System.out.println("Task " + taskId + " completed.");
            });
        }

        // 提交任务到线程池执行
        for (Runnable task : tasks) {
            executorService.submit(task);
        }

        // 关闭线程池(这会导致正在执行的任务完成后,线程池不再接受新任务)
        executorService.shutdown();

        // 等待所有任务执行完毕
        while (!executorService.isTerminated()) {
            // 等待线程池终止
        }

        // 输出任务执行总次数
        System.out.println("Total tasks completed: " + taskCounter.sum());
    }
}

首先创建了一个LongAdder对象taskCounter用于统计任务执行次数。然后,我们创建了一个固定大小的线程池executorService,用于并发执行任务。


接下来,我们创建了一个包含100个任务的列表tasks。每个任务都是一个Runnable对象,在其run方法中,我们模拟了任务执行的时间(通过Thread.sleep方法),并在任务执行完毕后使用LongAdder的increment方法增加计数器。


然后,我们将这些任务提交到线程池执行,并关闭线程池以拒绝新任务的提交。我们使用executorService.isTerminated()方法检查线程池是否已终止(即所有任务都已执行完毕),并在所有任务执行完毕后输出任务执行的总次数(通过LongAdder的sum方法获取)。


需要注意的是,在实际应用中,我们可能需要更精细地控制任务的提交和执行过程,例如使用CountDownLatch、CyclicBarrier或Semaphore等并发工具类来协调多个线程的执行顺序或限制并发数。此外,对于需要长时间运行的任务或需要频繁更新计数器的场景,我们可以考虑使用其他的并发容器或数据结构来优化性能。


二、LongAdder的性能优势

AtomicLong相比,LongAdder在高并发场景下的性能优势主要体现在以下几个方面:

  1. 减少线程间的竞争LongAdder内部维护了多个变量,每个线程对其中一个变量进行操作,从而减少了线程间的竞争。这使得在高并发场景下,LongAdder的性能优于AtomicLong
  2. 适用于统计和计数场景LongAdder适用于统计和计数场景,如记录某个方法的调用次数、统计某个事件的发生次数等。在这些场景中,我们不需要关心中间状态,只需要获取最终的总和。

然而,需要注意的是,LongAdder并不适用于所有场景。在需要精确控制中间状态的场景中(如需要获取任意时刻的精确值),AtomicLong可能更合适。此外,LongAdder的sum方法可能会比AtomicLong的get方法更耗时,因为它需要遍历内部的所有变量并求和。

三、LongAdder的实现原理

LongAdder的实现原理是基于分段锁和并发控制的思想,通过内部维护多个变量来减少线程间的竞争,从而提高并发性能。下面我们将深入分析LongAdder的实现原理。

1. 分段锁思想

LongAdder内部维护了一个或多个Cell对象,每个Cell对象包含一个长整型变量。这些Cell对象构成了一个数组,数组的大小通常是2的幂次方,以便使用位运算快速定位。每个线程在对LongAdder进行操作时,会根据当前线程的哈希码通过特定的哈希算法选择一个Cell对象进行操作。这种分段锁的思想类似于ConcurrentHashMap中的分段锁机制,通过将数据分散到多个段(Cell)上,减少了线程间的竞争。

2. 并发控制

当线程对LongAdder进行操作时,它会首先尝试获取对应Cell对象的锁(通过CAS操作实现)。如果成功获取锁,则线程会安全地更新该Cell对象的值。如果失败,则线程会尝试获取其他Cell对象的锁,或者更新base变量。这种并发控制机制确保了在高并发场景下,多个线程可以同时进行加法操作,而不会相互阻塞。


需要注意的是,LongAdder并不保证每个线程都固定地操作同一个Cell对象。当线程竞争同一个Cell对象失败时,它会尝试获取其他Cell对象的锁。这种灵活性使得LongAdder能够更好地适应动态变化的并发环境。

3. 变量合并与求和

当需要获取LongAdder的总和时,会遍历内部的所有Cell对象并将它们的值累加起来,然后再加上base变量的值。这个过程可能需要花费一些时间,因为需要遍历整个Cell数组。然而,在实际应用中,我们通常不需要频繁地获取总和,而是更关注于并发性能的优化。

需要指出的是,虽然LongAdder提供了比AtomicLong更高的吞吐量,但它并不适用于所有场景。在需要精确控制中间状态的场景中(如需要获取任意时刻的精确值),AtomicLong可能更合适。此外,LongAdder的sum方法可能会比AtomicLong的get方法更耗时,因为它需要遍历内部的所有变量并求和。因此,在选择使用LongAdder还是AtomicLong时,需要根据实际需求进行权衡和选择。


总之,LongAdder通过分段锁和并发控制的思想实现了高并发场景下的长整型加法操作优化。它内部维护了多个变量来减少线程间的竞争,并提供了灵活的并发控制机制以适应动态变化的并发环境。然而,在使用LongAdder时需要注意其适用场景和限制,并根据实际需求选择合适的并发工具类。


四、总结

LongAdder是Java并发库中的一个非常有用的工具类,它提供了比AtomicLong更高的吞吐量,适用于高并发场景下的统计和计数操作。然而,在使用LongAdder时,我们需要注意其适用场景和限制,并根据实际需求选择合适的并发工具类。


相关文章
|
29天前
|
Java
Java的CAS机制深度解析
CAS(Compare-And-Swap)是并发编程中的原子操作,用于实现多线程环境下的无锁数据同步。它通过比较内存值与预期值,决定是否更新值,从而避免锁的使用。CAS广泛应用于Java的原子类和并发包中,如AtomicInteger和ConcurrentHashMap,提升了并发性能。尽管CAS具有高性能、无死锁等优点,但也存在ABA问题、循环开销大及仅支持单变量原子操作等缺点。合理使用CAS,结合实际场景选择同步机制,能有效提升程序性能。
|
13天前
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
149 0
|
9天前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
267 100
|
2月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
13天前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
66 1
|
2月前
|
存储 缓存 算法
Java数据类型与运算符深度解析
本文深入解析Java中容易混淆的基础知识,包括八大基本数据类型(如int、Integer)、自动装箱与拆箱机制,以及运算符(如&与&&)的使用区别。通过代码示例剖析内存布局、取值范围及常见陷阱,帮助开发者写出更高效、健壮的代码,并附有面试高频问题解析,夯实基础。
|
2月前
|
算法 Java 测试技术
零基础学 Java: 从语法入门到企业级项目实战的详细学习路线解析
本文为零基础学习者提供完整的Java学习路线,涵盖语法基础、面向对象编程、数据结构与算法、多线程、JVM原理、Spring框架、Spring Boot及项目实战,助你从入门到进阶,系统掌握Java编程技能,提升实战开发能力。
113 0
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
641 29
|
7月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
186 4
|
7月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多
  • DNS