Java并发基础:原子类之AtomicInteger全面解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【2月更文挑战第2天】AtomicInteger类提供了线程安全的整数操作,它通过利用底层硬件的原子性指令,能够在多线程环境中高效地实现整数的无锁更新,避免了传统同步机制带来的性能开销,在高并发场景下成为计数器可大幅提高程序的执行效率,同时又保证了数据一致性。

Java并发基础:原子类之AtomicInteger全面解析 - 程序员古德

友情提示:

本文原创&首发于公众号:程序员古德

原文标题:Java并发基础:原子类之AtomicInteger全面解析

原文地址:https://mp.weixin.qq.com/s/cIsvEv-WHCeYjxP3wTqZNw

本文概要

AtomicInteger类提供了线程安全的整数操作,它通过利用底层硬件的原子性指令,能够在多线程环境中高效地实现整数的无锁更新,避免了传统同步机制带来的性能开销,在高并发场景下成为计数器可大幅提高程序的执行效率,同时又保证了数据一致性。

AtomicInteger核心概念

场景举例

模拟一个业务场景。

假设,有一个在线音乐媒体服务,在这个服务中,每首歌曲都有一个“喜欢”的计数器,每当有用户点击喜欢按钮时,这个计数器就会增加。由于这个系统的用户量很多,同一时间可能有成百上千的用户在点击喜欢按钮,因此这个“喜欢”计数器的增加操作必须在多线程环境下是安全的。

在这个模拟场景中,推荐使用AtomicInteger解决这个问题。可以将每首歌曲的“喜欢”计数器实现为一个AtomicInteger对象,每当有用户点击喜欢按钮时,就调用这个AtomicInteger对象的incrementAndGet()方法,这个方法会原子性地增加计数器的值,并返回增加后的结果,由于这个增加操作是原子的,因此不需要担心多个线程同时修改计数器时会导致数据不一致的问题。

技术思路

AtomicInteger类是一个用于处理整数类型数据的原子类,它属于java.util.concurrent.atomic包,主要用于解决多线程并发访问和修改共享整数变量时可能出现的数据不一致问题。

在多线程环境中,如果多个线程同时对同一个共享变量进行修改,就可能会引发数据不一致的情况,因为线程之间的操作是相互独立的,它们对共享变量的读取和修改操作可能会相互干扰。

为了避免这种情况,通常需要对共享变量的访问使用加锁进行同步处理,确保同一时间只有一个线程能够访问该变量。但是,使用加锁同步处理会带来一定的性能开销,因为它会阻塞线程的执行,但是,AtomicInteger的内部使用了硬件级别的原子操作来保证多线程环境下对共享变量的安全访问和修改。

AtomicInteger类提供了一系列原子操作的方法,如incrementAndGet()decrementAndGet()addAndGet()等,这些方法可以确保在多线程环境下对整数变量的增加、减少和设置等操作是原子的,即不可被中断的,因此,在这些方法执行期间,其他线程无法访问或修改该变量,从而保证了数据的一致性。

AtomicInteger使用案例

下面是一个简单的Java代码示例,演示了如何使用AtomicInteger类,代码中模拟多个线程同时对一个共享计数器进行增加操作,以展示AtomicInteger如何保证操作的原子性和线程安全,如下代码:

import java.util.concurrent.atomic.AtomicInteger;  

public class AtomicIntegerDemo {
   
     

    // 使用AtomicInteger作为共享计数器  
    private static AtomicInteger sharedCounter = new AtomicInteger(0);  

    public static void main(String[] args) {
   
     
        // 启动5个线程,每个线程将对共享计数器增加100次  
        for (int i = 0; i < 5; i++) {
   
     
            new Thread(() -> {
   
     
                for (int j = 0; j < 100; j++) {
   
     
                    sharedCounter.incrementAndGet(); // 原子性地增加计数器的值  
                }  
            }).start();  
        }  

        // 为了演示效果,主线程休眠一段时间,等待所有子线程执行完毕  
        try {
   
     
            Thread.sleep(1000);  
        } catch (InterruptedException e) {
   
     
            e.printStackTrace();  
        }  

        // 打印最终计数器的值  
        System.out.println("Final Counter Value: " + sharedCounter.get());  
    }  
}

在这段代码中:

  1. 创建了一个AtomicInteger实例sharedCounter,并将其初始化为0,这个实例将作为多个线程共享的计数器。
  2. main方法中,启动了5个线程,每个线程内部执行一个循环,循环100次,每次循环都调用sharedCounter.incrementAndGet()方法来原子性地增加计数器的值。
  3. 由于启动了5个线程,并且每个线程都会增加计数器100次,所以在没有线程安全问题的情况下,最终计数器的值应该是500。

AtomicInteger核心API

AtomicInteger类提供了对整数进行原子操作的机制,这些原子操作在多线程环境中特别有用,因为它们可以保证对整数的读取、写入和更新操作的原子性,从而避免线程安全问题。以下是AtomicInteger类中一些常用方法的含义:

  1. int get()
    • 获取当前的值。
  2. void set(int newValue)
    • 设置当前值为给定的值。
  3. void lazySet(int newValue)
    • 最终设置为给定值,但允许之后的其他内存操作重新排序(即不保证立即可见性给其他线程)。
  4. boolean compareAndSet(int expect, int update)
    • 如果当前值等于预期值,则以原子方式将该值设置为给定的更新值,如果更新成功,则返回true,否则返回false
  5. int getAndSet(int newValue)
    • 以原子方式设置为给定值,并返回旧值。
  6. int getAndIncrement()
    • 以原子方式将当前值加1,并返回旧值。
  7. int getAndDecrement()
    • 以原子方式将当前值减1,并返回旧值。
  8. int getAndAdd(int delta)
    • 以原子方式将给定的值加到当前值,并返回旧值。
  9. int incrementAndGet()
    • 以原子方式将当前值加1,并返回新值。
  10. int decrementAndGet()
    • 以原子方式将当前值减1,并返回新值。
  11. int addAndGet(int delta)
    • 以原子方式将给定的值加到当前值,并返回新值。
  12. int updateAndGet(IntUnaryOperator updateFunction)
    • 使用给定的函数以原子方式更新当前值,并返回更新后的值,该函数接受当前值并计算一个新值。
  13. boolean weakCompareAndSet(int expect, int update)
    • 如果当前值等于预期值,则尝试以原子方式将该值设置为给定的更新值,这个方法可能会失败,即使当前值与预期值相等,因此它被称为“weak”,如果更新成功,则返回true,否则返回false
  14. int getAndUpdate(IntUnaryOperator updateFunction)
    • 使用给定的函数以原子方式更新当前值,并返回旧值。该函数接受当前值并计算一个新值。
  15. int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)
    • 使用给定的累加函数和值以原子方式更新当前值,并返回旧值。该函数接受两个参数:一个是当前值,另一个是给定的值x,并计算一个新值。
  16. int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)
    • 使用给定的累加函数和值以原子方式更新当前值,并返回新值。该函数接受两个参数:一个是当前值,另一个是给定的值x,并计算一个新值。

AtomicInteger技术原理

AtomicInteger类它用于实现整数的原子操作,在多线程环境中,原子操作可以确保数据的一致性和线程安全,AtomicInteger通过硬件级别的原子操作(例如,通过compare-and-swap即CAS操作)来实现这些保证。

实现原理

AtomicInteger的实现基于以下几个关键概念:

  1. Unsafe类Unsafe类是Java中的一个底层类,提供了硬件级别的原子操作,这个类通常不直接暴露给普通Java应用开发者使用,而是被内部类如AtomicInteger所使用,Unsafe类提供了如compareAndSwapInt等方法,这些方法可以原子地更新内存中的值。
  2. volatile关键字AtomicInteger中的值被声明为volatile,这意味着这个值的读取和写入操作会从主内存中直接进行,而不是从线程的本地缓存中进行,这确保了所有线程都能看到最新的值。
  3. CAS(Compare-And-Swap)操作:CAS操作是一个原子操作,它包括三个操作数——内存位置(V)、预期原值(A)和新值(B),如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置的值更新为新值B,否则,处理器不做任何操作,无论哪种情况,它都会在CAS指令之前返回该位置的值,这一过程是原子的,也就是说在执行过程中不会被其他线程打断。

底层算法

AtomicInteger类中的每个方法都使用了上述的一个或多个概念来实现其原子性,例如:

  • get()方法:由于内部值被声明为volatile,因此每次调用get()都会直接从主内存中读取最新的值。
  • set(int newValue)方法:直接设置新值到内部volatile变量,由于volatile的特性,这个新值会立即被写入主内存,并且对所有线程可见。
  • compareAndSet(int expect, int update)方法:这个方法使用Unsafe类的compareAndSwapInt方法来实现CAS操作,如果当前值与期望值expect相等,则更新为update值,并返回true;否则不做任何操作并返回false
  • incrementAndGet()decrementAndGet()方法:这些方法内部使用了一个循环,通过CAS操作尝试更新值,如果更新成功,则返回新值;如果更新失败(由于并发修改),则重新尝试直到成功为止,这是一种称为“自旋锁”的技术。
  • addAndGet(int delta)等方法:类似地,这些方法也使用CAS操作在循环中尝试更新值,直到成功为止,它们可能涉及更复杂的计算或转换函数,但基本原理是相同的。

伪代码实现

AtomicInteger类提供了许多实用的工具方法,如int getAndIncrement()int getAndDecrement()int getAndSet(int newValue)等,下面是AtomicInteger类中一些核心方法的伪代码案例,用来帮助理解AtomicInteger类,如下代码:

import sun.misc.Unsafe;

public class AtomicInteger {
   
   
    // 使用Unsafe类获取对变量的偏移量,以便进行内存操作
    private static final Unsafe unsafe = getUnsafe();
    private static final long valueOffset;

    static {
   
   
        try {
   
   
            // 获取value字段的偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
   
   
            throw new Error(e);
        }
    }

    // 状态变量,存储整数值
    private volatile int value;

    public AtomicInteger(int initialValue) {
   
   
        value = initialValue;
    }

    // 基于CAS的原子递增方法
    public final int incrementAndGet() {
   
   
        while (true) {
   
   
            // 读取当前值
            int current = get();
            // 计算新值
            int next = current + 1;
            // 尝试原子地更新值,如果当前值未变,则更新成功并返回新值;否则循环重试
            if (compareAndSet(current, next)) {
   
   
                return next;
            }
        }
    }

    // CAS操作
    private boolean compareAndSet(int expect, int update) {
   
   
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    // 获取当前值
    public final int get() {
   
   
        return value;
    }

    // 其他如decrementAndGet、getAndIncrement等方法也类似实现...
}

上述代码中:

  • volatile关键字确保了对value变量的读写操作具有内存可见性。
  • Unsafe类提供了硬件级别的原子操作支持,例如compareAndSwapInt方法,它是一个原子操作,比较对象内存中的某个位置的值是否为期望值(expect),如果是则更新为新的给定值(update)。

实际的AtomicInteger类在JDK内部实现会更复杂,包括处理内存模型和并发优化等细节,但上述伪代码足以展现其核心思想,通过循环CAS的方式实现无锁的原子操作。

自我总结

Java并发基础:原子类之AtomicBoolean全面解析 - 程序员古德

AtomicInteger类通过底层的硬件支持,确保在多线程环境下对整数的操作是线程安全的。

优点

  1. 线程安全:AtomicInteger通过原子操作保证了并发访问时的数据一致性,避免了使用synchronized等重量级锁带来的性能开销。
  2. 性能高效:相比传统的同步机制,AtomicInteger提供了更高的吞吐量,因为它避免了线程间的阻塞和上下文切换。

缺点

  1. 使用场景限制:AtomicInteger适用于简单的原子操作,但在复杂的并发场景中可能不足以应对所有的同步需求。
  2. 内存消耗:虽然AtomicInteger比使用锁的开销小,但它仍然比非原子的整数类型占用更多的内存。
  3. ABA问题:在使用CAS操作时,可能会遇到ABA问题,即一个值从A变成B又变回A,CAS会认为值没有发生变化,尽管中间可能经历了其他操作。

使用建议

当需要在多线程环境中安全地更新整数时,推荐使用AtomicInteger类,但在使用之前,应仔细评估其是否满足特定的同步需求,对于更复杂的并发控制,可能需要考虑使用更强大的同步机制,如ReentrantLock或CyclicBarrier等

关注我,每天学习互联网编程技术 - 程序员古德

END!
END!
END!

往期回顾

精品文章

Java并发基础:concurrent Flow API全面解析

Java并发基础:CopyOnWriteArraySet全面解析

Java并发基础:ConcurrentSkipListMap全面解析

Java并发基础:ConcurrentSkipListSet全面解析!

Java并发基础:SynchronousQueue全面解析!

相关文章
|
5天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
3天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
13 2
|
2天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
9天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
18 3
|
7天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
22 1
|
2天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
6 0
|
6月前
|
数据可视化 Java 测试技术
Java 编程问题:十一、并发-深入探索1
Java 编程问题:十一、并发-深入探索
74 0
|
3月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
68 1
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。

推荐镜像

更多