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全面解析!

相关文章
|
3天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
3天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
3天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
3天前
|
Java
Java输入输出流详细解析
Java输入输出流详细解析
Java输入输出流详细解析
|
4天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
4天前
|
存储 Java C++
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
17 0
|
9天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
11天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
11天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
11天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
24 0

推荐镜像

更多