解密Java并发中的秘密武器:LongAdder与Atomic类型

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

解密Java并发中的秘密武器:LongAdder与Atomic类型

    • 前言
    • 引言:为何需要原子操作?
      • 挑战和问题:
      • 原子操作的概念和重要性:
    • AtomicInteger和AtomicLong:Java的原子变量
      • AtomicInteger 特性:
      • AtomicLong 特性:
      • 如何使用这两个类实现线程安全的计数器:
    • LongAdder:高并发环境下的性能杀手
      • 为什么 LongAdder 在高并发环境下更具优势?
      • 使用 LongAdder 解决传统 Atomic 类型的性能瓶颈问题:
    • 适用场景比较:何时选择何种原子类型?
      • AtomicInteger vs. AtomicLong:
      • LongAdder:
      • 最佳选择策略:
    • 最佳实践和注意事项
      • 最佳实践:
      • 注意事项和常见陷阱解析:

前言

在多线程编程的世界里,数据的安全性和性能是两个永远并存的挑战。本文将向你介绍Java并发领域的三位明星:LongAdder、AtomicInteger和AtomicLong。就像是编程世界的三把锋利武器,它们能够帮你在并发的战场上立于不败之地。现在,让我们揭开它们的神秘面纱。

引言:为何需要原子操作?

在并发编程中,原子操作是一种不可分割的操作,它要么完全执行成功,要么完全不执行,不会被中断。并发编程涉及多个线程或进程同时访问和修改共享的数据,这会引发一系列挑战,其中原子操作的概念和重要性变得至关重要。

挑战和问题:

  1. 竞态条件(Race Condition): 当多个线程同时访问和修改共享数据时,如果没有足够的同步机制,可能导致不确定的结果。竞态条件可能导致数据不一致性和程序行为的不确定性。

  2. 死锁(Deadlock): 当多个线程相互等待对方释放资源时,可能发生死锁。这会导致线程无法继续执行,造成系统的停滞。

  3. 数据不一致性: 如果没有适当的同步,多个线程对共享数据的并发修改可能导致数据不一致,破坏程序的正确性。

原子操作的概念和重要性:

  1. 不可分割性: 原子操作是一种不可分割的操作单元,要么全部执行成功,要么全部不执行。这确保了并发环境下对共享数据的一致性。

  2. 确保线程安全: 原子操作的特性确保在多线程环境中执行时,不会发生竞态条件,从而避免了潜在的数据损坏和程序行为的不确定性。

  3. 提供同步机制: 在并发编程中,原子操作通常与锁、CAS(Compare and Swap)等同步机制结合使用,以确保对共享数据的正确访问和修改。

  4. 保证一致性: 使用原子操作可以保证在并发环境中共享数据的一致性,防止因为并发操作导致的数据不一致问题。

在编写并发程序时,使用原子操作是一种有效的手段来确保程序的正确性和可靠性。Java中的java.util.concurrent包提供了丰富的原子操作类,如AtomicIntegerAtomicLong等,用于支持在多线程环境下的原子操作。理解并正确使用原子操作是并发编程中至关重要的一部分。

AtomicInteger和AtomicLong:Java的原子变量

AtomicIntegerAtomicLong是Java中java.util.concurrent.atomic包下的两个原子变量类,用于提供在多线程环境下的原子操作。它们分别用于对intlong类型的变量执行原子操作,保证了这些操作的不可分割性。

AtomicInteger 特性:

  1. 原子递增和递减: 提供了incrementAndGet()decrementAndGet()方法,分别用于原子递增和递减操作。

  2. 原子加减: 提供了addAndGet(int delta)方法,用于以原子方式将指定值与当前值相加。

  3. 原子更新: 提供了updateAndGet(IntUnaryOperator updateFunction)方法,可以通过自定义函数更新值,确保原子性。

import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger counter = new AtomicInteger(0);

int result = counter.incrementAndGet(); // 原子递增
result = counter.addAndGet(5); // 原子加5
result = counter.updateAndGet(x -> x * 2); // 使用自定义函数原子更新

AtomicLong 特性:

AtomicLong类的特性与AtomicInteger类类似,但是适用于long类型的变量。

import java.util.concurrent.atomic.AtomicLong;

AtomicLong counter = new AtomicLong(0);

long result = counter.incrementAndGet(); // 原子递增
result = counter.addAndGet(5); // 原子加5
result = counter.updateAndGet(x -> x * 2); // 使用自定义函数原子更新

如何使用这两个类实现线程安全的计数器:

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadSafeCounter {

    private AtomicInteger counter = new AtomicInteger(0);

    public int increment() {
        return counter.incrementAndGet();
    }

    public int getCount() {
        return counter.get();
    }

    public static void main(String[] args) {
        ThreadSafeCounter threadSafeCounter = new ThreadSafeCounter();

        // 多线程同时递增计数器
        Runnable incrementTask = () -> {
            for (int i = 0; i < 1000; i++) {
                threadSafeCounter.increment();
            }
        };

        // 创建多个线程执行递增任务
        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出计数器的最终值
        System.out.println("Final Count: " + threadSafeCounter.getCount());
    }
}

在上述示例中,ThreadSafeCounter类使用AtomicInteger来实现一个线程安全的计数器。通过incrementAndGet()方法进行原子递增操作,保证了多线程环境下对计数器的安全访问。这确保了在并发环境中计数器的正确性和一致性。

LongAdder:高并发环境下的性能杀手

LongAdder是Java中java.util.concurrent.atomic包下的另一个原子变量类,专门设计用于在高并发环境下提供更好性能的一种解决方案。它主要针对在高度竞争情况下,多线程频繁更新一个计数器的场景,提供了一种高效的并发累加器。

为什么 LongAdder 在高并发环境下更具优势?

  1. 减少竞争: 在高并发情况下,使用传统的AtomicIntegerAtomicLong时,多个线程可能会争夺同一个原子变量的更新,导致性能瓶颈。而LongAdder采用了一种分段的思想,将一个变量分成多个小的段,每个线程只更新其中一个段,最后再将这些段的值相加。这样可以大大减少线程之间的竞争,提高了性能。

  2. 懒惰初始化: LongAdder在初始化时不会分配一个连续的数组,而是在需要的时候再进行懒惰初始化。这减少了初始开销,提高了并发更新时的性能。

  3. 分段累加: LongAdder内部使用Cell数组,每个Cell代表一个独立的段,线程更新时通过hash定位到不同的Cell,实现分段累加。这种方式在高并发情况下避免了对同一个变量的竞争,降低了锁的粒度,提高了吞吐量。

使用 LongAdder 解决传统 Atomic 类型的性能瓶颈问题:

import java.util.concurrent.atomic.LongAdder;

public class HighConcurrencyCounter {

    private LongAdder counter = new LongAdder();

    public void increment() {
        counter.increment();
    }

    public long getCount() {
        return counter.sum();
    }

    public static void main(String[] args) {
        HighConcurrencyCounter highConcurrencyCounter = new HighConcurrencyCounter();

        // 多线程同时递增计数器
        Runnable incrementTask = () -> {
            for (int i = 0; i < 1000; i++) {
                highConcurrencyCounter.increment();
            }
        };

        // 创建多个线程执行递增任务
        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(incrementTask);

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出计数器的最终值
        System.out.println("Final Count: " + highConcurrencyCounter.getCount());
    }
}

在上述示例中,HighConcurrencyCounter类使用LongAdder来实现一个线程安全的计数器。LongAdderincrement()方法用于原子递增操作,而getCount()方法使用了sum()方法来获取最终的计数值。这种方式在高并发场景下表现更好,相对于传统的原子变量,LongAdder能够更好地处理并发累加操作,提高了性能。

适用场景比较:何时选择何种原子类型?

选择合适的原子类型取决于具体的应用场景和性能要求。在比较AtomicIntegerAtomicLongLongAdder的优劣时,以下是一些考虑因素和适用场景的比较:

AtomicInteger vs. AtomicLong:

  1. 适用类型:

    • AtomicInteger适用于需要原子操作的int类型计数器。
    • AtomicLong适用于需要原子操作的long类型计数器。
  2. 内存占用:

    • AtomicIntegerAtomicLong都是单个变量,占用固定的内存空间。
  3. 性能特点:

    • AtomicIntegerAtomicLong适用于低并发或中等并发场景。
    • 在高并发环境下,可能会有较多线程竞争同一个变量,可能出现性能瓶颈。

LongAdder:

  1. 适用类型:

    • LongAdder适用于需要原子操作的long类型计数器。
    • LongAdder相比于AtomicLong更适合高并发场景。
  2. 内存占用:

    • LongAdder内部使用了Cell数组,适应了高并发场景,但在低并发场景下可能占用更多内存。
  3. 性能特点:

    • LongAdder在高并发场景下性能更好,因为它通过分段技术降低了线程竞争的程度。
    • 在低并发场景下,性能可能略低于AtomicLong

最佳选择策略:

  1. 低并发场景:

    • 如果在低并发场景下,选择AtomicIntegerAtomicLong即可满足要求。它们的简单性和性能表现可以满足低并发的需求。
  2. 中等并发场景:

    • 在中等并发场景下,仍可以选择AtomicIntegerAtomicLong。性能可能较高,并且不会引入过多的内存占用。
  3. 高并发场景:

    • 在高并发场景下,建议使用LongAdder。它通过分段技术降低了线程之间的竞争,适应了高并发的需求。
  4. 内存限制:

    • 如果有内存限制,需要权衡内存占用和性能,可以考虑在高并发场景下使用LongAdder,在低并发场景下使用AtomicIntegerAtomicLong

总体而言,选择适当的原子类型需要综合考虑并发级别、内存占用和性能要求。在实际应用中,可以进行性能测试和评估,根据具体情况选择最合适的原子类型。

最佳实践和注意事项

最佳实践:

  1. 选择合适的原子类型:

    • 根据需要选择AtomicIntegerAtomicLongLongAdder,考虑并发级别、性能需求和内存占用。
  2. 慎用get操作:

    • 使用get操作获取原子变量的值可能会导致性能问题。在高并发场景下,频繁调用get可能会损失LongAdder的优势,因为它需要合并各个段的值。
  3. 合理使用accumulatereset

    • LongAdder提供了accumulatereset方法,可以用于累加和重置计数器。合理使用这些方法,根据业务需求决定何时重置计数器。
  4. 性能测试和调优:

    • 在实际应用中进行性能测试,并根据测试结果进行调优。不同的场景可能需要不同的原子类型,通过测试找到最适合的选择。
  5. 注意内存占用:

    • 注意LongAdder在低并发场景下可能占用较多内存,尤其是在需要大量计数器的情况下。根据内存限制权衡内存占用和性能。

注意事项和常见陷阱解析:

  1. 避免过度使用原子操作:

    • 不是所有的计数操作都需要原子性,过度使用原子操作可能引入不必要的性能开销。根据实际需求选择合适的同步机制。
  2. 避免竞态条件:

    • 使用原子类型并不意味着完全避免竞态条件。在某些情况下,可能需要使用更高级的同步机制,如锁,来确保多个原子操作的原子性。
  3. 避免过分追求性能:

    • 在低并发场景下,过分追求性能可能会导致代码复杂性增加。只有在高并发环境下,选择更复杂的原子类型才是值得的。
  4. 不同 JVM 版本行为差异:

    • 不同版本的Java虚拟机可能对原子类型的实现有一些差异,尤其是在一些早期版本中。确保使用的JVM版本对原子类型的实现没有不良影响。
  5. 避免重复的原子操作:

    • 在一些情况下,可能会发现一些操作是冗余的,导致性能损失。定期检查代码,确保原子操作的使用是必要的。

总体而言,使用原子类型时要根据具体需求进行权衡,了解其实现原理,进行性能测试,并谨慎避免常见的陷阱。合理使用原子类型可以提高并发程序的性能和正确性。

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

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

相关文章

leetcode hot100 组合总和Ⅲ

本题中&#xff0c;要求我们求在1-9范围内&#xff0c;满足k个数的和为n的组合&#xff08;组合是无序的&#xff0c;并且题目中要求不可以重复&#xff09;。 这种组合问题依旧需要用回溯算法来解决。因为我们没办法控制产生k层for循环。回溯算法的过程是构建树结构&#xff…

【并发编程】锁死的问题——如何解决?以及如何避免?

目录 1.如何解决 一、死锁的定义和原因 1.1 定义 1.2 原因 二、常见的死锁场景 2.1 线程间相互等待资源 2.2 嵌套锁的循环等待 2.3 对资源的有序请求 三、死锁排查的方法 3.1 使用jstack命令 3.2 使用jconsole 3.3 使用VisualVM 四、常见的解决方案 4.1 避免嵌套锁…

16、Kafka ------ SpringBoot 整合 Kafka (配置 Kafka 属性 及对应的 属性处理类 解析)

目录 配置 Kafka 及对应的 属性处理类配置KafkaKafka配置属性的约定代码演示生产者相关的配置消费者相关的配置 代码&#xff08;配置文件&#xff09;application.properties 配置 Kafka 及对应的 属性处理类 配置Kafka spring.kafka.* 开头的配置属性&#xff0c;这些属性将由…

【Vue2 + ElementUI】分页el-pagination 封装成公用组件

效果图 实现 &#xff08;1&#xff09;公共组件 <template><nav class"pagination-nav"><el-pagination class"page-area" size-change"handleSizeChange" current-change"handleCurrentChange":current-page"c…

ChatGPT模型大更新!全新大、小文本嵌入模型,API价格大降价!

1月26日凌晨&#xff0c;OpenAI在官网对ChatGPT Turbo模型&#xff08;修复懒惰行为&#xff09;&#xff0c;免费的审核模型&#xff0c;并对新的GPT-3.5 Turbo模型API进行了大幅度降价。模型进行了大更新&#xff0c;发布了两款全新大、小文本嵌入模型&#xff0c;全新的GPT-…

600条最强Linux命令总结,建议收藏

今天&#xff0c;带来一篇 Linux 命令总结的非常全的文章&#xff0c;也是我们平时工作中使用率非常高的操作命令&#xff0c;命令有点多&#xff0c;建议小伙伴们可以先收藏后阅读。 在此之前先给大家分享一波黑客学习资料 1. 基本命令 uname -m 显示机器的处理器架构 uname …

超级万能DIY模块化电商小程序源码系统 带完整的搭建教程

随着电商市场的不断扩大&#xff0c;越来越多的商家涌入电商平台&#xff0c;竞争愈发激烈。为了在众多竞争对手中脱颖而出&#xff0c;商家需要打造一款个性化、功能强大的电商小程序&#xff0c;以吸引更多的用户。而超级万能DIY模块化电商小程序源码系统正是为了满足商家的这…

第二证券:大金融板块逆势护盘 北向资金尾盘加速净流入

周一&#xff0c;A股商场低开低走&#xff0c;沪指收盘失守2800点。截至收盘&#xff0c;上证综指跌2.68%&#xff0c;报2756.34点&#xff1b;深证成指跌3.5%&#xff0c;报8479.55点&#xff1b;创业板指跌2.83%&#xff0c;报1666.88点。沪深两市合计成交额7941亿元&#xf…

基于Servlet实现博客系统

目录 一、功能和效果 1、实现的功能 2、页面效果 二、功能具体实现 1、数据库 &#xff08;1&#xff09;设计数据库 &#xff08;2&#xff09;创建数据库表 &#xff08;3&#xff09;实现对blogs表和users表的操作并封装 2、登陆功能实现 &#xff08;1&#xff09…

筑梦前行!苏州金龙荣获影响中国客车业两项大奖

2024年1月19日&#xff0c;第十八届影响客车业年度盘点颁奖典礼在合肥举行。活动期间&#xff0c;众多奖项如期揭晓&#xff0c;经组委会评审团评定&#xff0c;苏州金龙海格睿星KLQ5041XSWEV1、旅行家KLQ6127YEV1N分别荣获“定制旅游客车之星”大奖和“新能源客车推荐车型”大…

搜索引擎Elasticsearch了解

1.Lucene 是什么? 2.模块介绍 Lucene是什么: 一种高性能,可伸缩的信息搜索(IR)库 在2000年开源,最初由鼎鼎大名的Doug Cutting开发 是基于Java实现的高性能的开源项目 Lucene采用了基于倒排表的设计原理,可以非常高效地实现文本查找,在底层采用了分段的存储模式,使它在读…

Your lDE is missing natures to properly support your projects

错误提示 Your lDE is missing natures to properly support your projects. Some extensions on the Eclipse Marketplace can be installed to support those natures.解决方案 打开项目文件&#xff0c;找到.project 文件&#xff0c;用编辑器打开 找到 把下图效果图中相关…

【方法重写】精英必看:详解Java中的方法重写!!

前言 当我在学到“面向对象”这块内容的时候&#xff0c;学到了一个概念&#xff0c;那就是“方法的重写”。重写又叫覆盖&#xff0c;英文名为“Override”。虽然”重写”、 ”覆盖”、“Override”这些名词都很容易记住&#xff0c;但很多人&#xff08;包括我&#xff09;并…

【数据库学习】pg安装与运维

1&#xff0c;安装与配置 #安装 yum install https:....rpm1&#xff09;安装目录 bin目录&#xff1a;二进制可执行文件目录&#xff0c;此目录下有postgres、psql等可执行程序&#xff1b;pg_ctl工具在此目录&#xff0c;可以通过pg_ctl --help查看具体使用。 conf目录&…

Journal of Intelligent Fuzzy Systems期刊的格式要求

摘要 摘要应该清晰、具有描述性&#xff0c;自说明&#xff0c;并且不超过200字。同时&#xff0c;它应该适合在文摘服务中发布。请在摘要中不要包含参考文献或公式。 关键词&#xff1a;关键词一&#xff0c;关键词二&#xff0c;关键词三&#xff0c;关键词四&#xff0c;关…

电商API接口接入|电商爬虫实践附代码案例

1.爬虫是什么 首先应该弄明白一件事&#xff0c;就是什么是爬虫&#xff0c;为什么要爬虫&#xff0c;百度了一下&#xff0c;是这样解释的&#xff1a;网络爬虫&#xff08;又被称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追…

银行数据仓库体系实践(6)--调度系统

调度系统是数据仓库的重要组成部分&#xff0c;也是每个银行或公司一个基础软件或服务&#xff0c;需要在全行或全公司层面进行规划&#xff0c;在全行层面统一调度工具和规范&#xff0c;由于数据类系统调度作业较多&#xff0c;交易类系统批量优先级高&#xff0c;为不互相影…

算法------(10)堆

例题&#xff1a;&#xff08;1&#xff09;AcWing 838. 堆排序 我们可以利用一个一维数组来模拟堆。由于堆本质上是一个完全二叉树&#xff0c;他的每个父节点的权值都小于左右子节点&#xff0c;而每个父节点编号为n时&#xff0c;左节点编号为2*n&#xff0c;右节点编号为2*…

10. UE5 RPG使用GameEffect创建血瓶修改角色属性

前面我们通过代码实现了UI显示角色的血量和蓝量&#xff0c;并实现了初始化和在数值变动时实时更新。为了测试方便&#xff0c;没有使用GameEffect去修改角色的属性&#xff0c;而是通过代码直接修改的数值。 对于GameEffect的基础&#xff0c;这里不再讲解&#xff0c;如果需要…

跨境电商的网络为什么要用云桥通SDWAN企业组网?

传统的WAN连接通常由交换机和路由器构成&#xff0c;然而&#xff0c;随着企业内部网络的扩张和变革&#xff0c;传统WAN的管理和配置变得复杂繁琐。云桥通SDWAN组网采用了较新的技术方式&#xff0c;通过中央控制器对局域网设备进行管理和配置&#xff0c;从而实现了更为灵活、…