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

简介: 【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全面解析!

相关文章
|
29天前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
2月前
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
93 0
|
1天前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
44 7
|
1月前
|
存储 安全 Java
【Java并发】【原子类】适合初学体质的原子类入门
什么是CAS? 说到原子类,首先就要说到CAS: CAS(Compare and Swap) 是一种无锁的原子操作,用于实现多线程环境下的安全数据更新。 CAS(Compare and Swap) 的
61 15
【Java并发】【原子类】适合初学体质的原子类入门
|
1月前
|
缓存 监控 Java
深入解析java正则表达式
本文深入解析Java正则表达式的应用,从基础概念到实际开发技巧全面展开。正则表达式是一种强大的文本处理工具,广泛应用于格式验证、搜索替换等场景。Java通过`Pattern`和`Matcher`类支持正则表达式,`Pattern.compile()`方法将正则字符串编译为高效模式对象。文章详细介绍了核心类的功能、常用正则语法及实际案例(如邮箱和电话号码验证)。掌握这些内容,可显著提升文本处理能力,满足多种开发需求。
63 1
|
2月前
|
存储 设计模式 Java
重学Java基础篇—ThreadLocal深度解析与最佳实践
ThreadLocal 是一种实现线程隔离的机制,为每个线程创建独立变量副本,适用于数据库连接管理、用户会话信息存储等场景。
105 5
|
2月前
|
存储 监控 安全
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
76 5
|
2月前
|
机器学习/深度学习 人工智能 Java
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
127 3
|
2月前
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
85 1
|
1月前
|
Java 编译器 API
Java Lambda 表达式:以 Foo 接口为例深入解析
本文深入解析了 Java 8 中 Lambda 表达式的用法及其背后的函数式接口原理,以 `Foo` 接口为例,展示了如何通过简洁的 Lambda 表达式替代传统匿名类实现。文章从 Lambda 基本语法、函数式接口定义到实际应用层层递进,并探讨默认方法与静态方法的扩展性,最后总结常见误区与关键点,助你高效优化代码!
52 0

推荐镜像

更多