Java中AtomicInteger原子类使用方法(子类,使用方法,Java,AtomicInteger.......)

feifei123 发布于 2025-09-17 阅读(3)
AtomicInteger通过CAS实现无锁原子操作,解决多线程下i++等非原子操作导致的竞态条件问题,相比synchronized避免了阻塞和上下文切换开销,在低竞争场景下性能更优。

java中atomicinteger原子类使用方法

Java中的

AtomicInteger
是一个用于在多线程环境下安全地操作整型数据的类,它通过无锁(lock-free)的原子操作来保证数据的一致性,避免了传统
synchronized
关键字或锁机制带来的性能开销和潜在死锁问题。简单来说,它能让你在并发场景下,像操作普通变量一样安全地对整数进行增减、比较并设置等操作,而不用担心数据混乱。

解决方案

在多线程编程中,我们经常需要对一个共享的计数器或状态变量进行操作。如果直接使用

int
类型,例如
count++
,这并非一个原子操作,它通常包括读取、修改、写入三个步骤。当多个线程同时执行时,就可能出现竞态条件(Race Condition),导致最终结果不正确。
AtomicInteger
正是为了解决这个问题而生。

它的核心机制是基于CAS(Compare-And-Swap,比较并交换)指令,这是一种CPU级别的原子操作。当你尝试更新

AtomicInteger
的值时,它会先比较当前值是否是你期望的旧值,如果是,则更新为新值;否则,表示其他线程已经修改了该值,当前操作会失败并重试,直到成功为止。这个过程是无锁的,因此在很多场景下比使用
synchronized
ReentrantLock
效率更高。

以下是一些

AtomicInteger
的常见使用方法和示例:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    // 创建一个初始值为0的AtomicInteger
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 示例1: 递增操作
        System.out.println("初始值: " + counter.get()); // 0

        // incrementAndGet():先递增,再返回新值
        int newValue1 = counter.incrementAndGet();
        System.out.println("递增后 (incrementAndGet): " + newValue1 + ", 当前值: " + counter.get()); // 1, 1

        // getAndIncrement():先返回旧值,再递增
        int oldValue1 = counter.getAndIncrement();
        System.out.println("递增后 (getAndIncrement): " + oldValue1 + ", 当前值: " + counter.get()); // 1, 2

        // 示例2: 递减操作
        // decrementAndGet():先递减,再返回新值
        int newValue2 = counter.decrementAndGet();
        System.out.println("递减后 (decrementAndGet): " + newValue2 + ", 当前值: " + counter.get()); // 1, 1

        // getAndDecrement():先返回旧值,再递减
        int oldValue2 = counter.getAndDecrement();
        System.out.println("递减后 (getAndDecrement): " + oldValue2 + ", 当前值: " + counter.get()); // 1, 0

        // 示例3: 加法操作
        // addAndGet(delta):将delta加到当前值上,并返回新值
        int newValue3 = counter.addAndGet(5);
        System.out.println("加5后 (addAndGet): " + newValue3 + ", 当前值: " + counter.get()); // 5, 5

        // getAndAdd(delta):返回旧值,然后将delta加到当前值上
        int oldValue3 = counter.getAndAdd(3);
        System.out.println("加3后 (getAndAdd): " + oldValue3 + ", 当前值: " + counter.get()); // 5, 8

        // 示例4: 比较并设置 (CAS)
        // compareAndSet(expectedValue, updateValue):如果当前值等于expectedValue,则更新为updateValue并返回true;否则返回false
        boolean casSuccess1 = counter.compareAndSet(8, 10); // 当前是8,期望也是8,更新为10
        System.out.println("CAS操作 (8 -> 10) 成功? " + casSuccess1 + ", 当前值: " + counter.get()); // true, 10

        boolean casSuccess2 = counter.compareAndSet(8, 12); // 当前是10,期望是8,不匹配,不更新
        System.out.println("CAS操作 (8 -> 12) 成功? " + casSuccess2 + ", 当前值: " + counter.get()); // false, 10

        // 示例5: 多线程计数器
        AtomicInteger multiThreadCounter = new AtomicInteger(0);
        int numThreads = 10;
        int incrementsPerThread = 1000;

        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    multiThreadCounter.incrementAndGet();
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join(); // 等待所有线程完成
        }

        System.out.println("多线程计数器最终值: " + multiThreadCounter.get()); // 应该总是 numThreads * incrementsPerThread = 10000
    }
}
为什么在多线程环境下,简单的
int
不够用?
AtomicInteger
如何解决并发问题?

你可能觉得,不就是个整数加减吗,有什么难的?但当多个线程同时访问并修改一个

int
变量时,问题就来了。以
i++
为例,它在底层通常被拆分为三个步骤:1. 读取
i
的当前值;2. 将
i
的值加1;3. 将新值写回
i
。设想一下,线程A读取了
i
的值为5,正准备加1;此时线程B也读取了
i
的值为5,也准备加1。线程A先完成了加1并写回6,紧接着线程B也完成了加1并写回6。结果,
i
最终是6,而不是我们期望的7。这就是典型的竞态条件,数据丢失了。

传统的解决方案是使用

synchronized
关键字或
ReentrantLock
来保护临界区,确保同一时间只有一个线程能访问和修改变量。但这引入了锁的开销,包括上下文切换、线程阻塞和唤醒等,在高并发场景下可能成为性能瓶颈,甚至可能导致死锁。

AtomicInteger
则另辟蹊径,它利用了CPU提供的CAS(Compare-And-Swap)指令。这个指令是原子性的,意味着它要么成功执行,要么不执行,不会被中断。当
AtomicInteger
执行
incrementAndGet()
这类操作时,它会尝试循环:获取当前值,计算新值,然后调用CAS指令。CAS指令会检查当前值是否仍是它之前获取的那个值(即没有被其他线程修改),如果是,就更新为新值并成功返回;如果不是,说明有其他线程捷足先登了,它会放弃当前操作,重新获取最新值,再次尝试。这个过程是“无锁”的,因为线程不会被阻塞,只是失败后重试,这被称为乐观锁或自旋锁。这种机制在低到中等竞争程度下,通常比传统锁有更好的性能表现,因为它避免了操作系统级别的线程调度开销。

燕雀光年 燕雀光年

一站式AI品牌设计平台,支持AI Logo设计、品牌VI设计、高端样机设计、AI营销设计等众多种功能

燕雀光年68 查看详情 燕雀光年
AtomicInteger
的常用方法有哪些?它们各自适用于什么场景?

AtomicInteger
提供了一系列原子操作方法,覆盖了常见的整数操作需求:

  • get()
    /
    set(int newValue)
    : 这是最基础的读写操作。
    get()
    原子地返回当前值,
    set()
    原子地将值设置为
    newValue
    。它们主要用于获取或设置变量的最新状态,比如在某个业务流程开始时初始化计数器,或在结束时读取最终结果。
  • incrementAndGet()
    /
    getAndIncrement()
    : 这两个是原子递增操作。
    • incrementAndGet()
      :先将值加1,然后返回新值。适用于需要知道递增后的最新值,比如生成下一个序列号。
    • getAndIncrement()
      :先返回当前值,然后将值加1。适用于需要使用当前值,并同时递增它,比如获取一个ID后立即为下一个ID做准备。
  • decrementAndGet()
    /
    getAndDecrement()
    : 与递增类似,这是原子递减操作。
    • decrementAndGet()
      :先将值减1,然后返回新值。
    • getAndDecrement()
      :先返回当前值,然后将值减1。
  • addAndGet(int delta)
    /
    getAndAdd(int delta)
    : 这是原子加法操作,可以指定一个增量
    delta
    • addAndGet(delta)
      :先将当前值加上
      delta
      ,然后返回新值。
    • getAndAdd(delta)
      :先返回当前值,然后将当前值加上
      delta
      。 这些方法非常适合处理计数器,比如统计某个事件发生的次数,或者在缓存中更新某个热点数据的访问量。
  • compareAndSet(int expectedValue, int updateValue)
    : 这是
    AtomicInteger
    最核心的方法,也是所有其他原子操作的基础。它尝试将当前值原子地设置为
    updateValue
    ,前提是当前值等于
    expectedValue
    。如果设置成功,返回
    true
    ;否则返回
    false
    。这个方法在实现复杂的无锁算法时非常有用,例如实现一个自旋锁、一个简单的状态机或者构建其他原子数据结构。
  • weakCompareAndSet(int expectedValue, int updateValue)
    : 这个方法与
    compareAndSet
    类似,但在某些内存模型下,它可能无法保证操作的顺序性(即不能保证发生在它之前的写操作对其他线程可见)。在大多数情况下,我们应该优先使用
    compareAndSet
    ,除非你对JMM(Java Memory Model)有深入理解,并且需要极致的性能优化,愿意承担更复杂的并发编程风险。
AtomicInteger
的性能表现如何?何时选择它而非
synchronized
ReentrantLock

AtomicInteger
的性能表现通常在低到中等竞争程度下优于传统的锁机制(如
synchronized
ReentrantLock
)。它的主要优势在于:

  1. 无锁(Lock-Free):
    AtomicInteger
    基于CAS操作,线程不会被阻塞,而是失败后重试。这意味着没有线程上下文切换的开销,也没有死锁的风险。当竞争不激烈时,线程很少需要重试,因此效率很高。
  2. 细粒度控制:
    AtomicInteger
    只针对单个变量进行原子操作,粒度非常细。而
    synchronized
    ReentrantLock
    通常会保护一个代码块,可能包含多个变量和复杂逻辑,粒度相对较粗。

然而,

AtomicInteger
并非万能药,它也有其适用场景和局限性:

何时选择

AtomicInteger

  • 简单的计数器或状态标志: 当你需要一个在多线程环境下安全递增/递减的计数器(如网站访问量、请求处理数),或者一个简单的布尔状态标志(通过
    AtomicBoolean
    ),
    AtomicInteger
    是理想选择。
  • 高并发、低竞争: 在许多线程同时尝试修改同一个变量,但每次修改的冲突概率不高的场景下,
    AtomicInteger
    的自旋重试机制能有效避免锁的开销。
  • 构建无锁数据结构:
    AtomicInteger
    是构建更复杂的无锁数据结构(如无锁队列、无锁栈)的基础组件之一。
  • 性能敏感的单变量操作: 当对单个整数变量的原子操作是性能瓶颈时,
    AtomicInteger
    能提供更优的性能。

何时选择

synchronized
ReentrantLock

  • 复杂临界区: 当你需要保护一个包含多个变量、复杂逻辑或需要维护多个操作原子性的代码块时,
    AtomicInteger
    就显得力不从心了。例如,更新一个账户余额不仅要修改金额,可能还要记录交易日志,这需要一个原子性的事务,而不是简单的单变量操作。
  • 高竞争、长时间持有锁: 在竞争非常激烈,或者锁需要被长时间持有的场景下,
    AtomicInteger
    的自旋重试可能会导致CPU空转,消耗大量CPU资源。此时,传统的阻塞锁(如
    ReentrantLock
    )可能更合适,因为它们会让失败的线程进入等待状态,释放CPU给其他线程。
  • 需要公平性或条件变量:
    ReentrantLock
    提供了公平锁选项,并且可以配合
    Condition
    实现更复杂的线程协作模式(如生产者-消费者模型),这些是
    AtomicInteger
    无法提供的。

总而言之,

AtomicInteger
是Java并发工具箱中一个非常实用的组件,它提供了一种高效、无锁的方式来处理单个整数变量的原子操作。理解其底层原理和适用场景,能帮助我们编写出更健壮、更高性能的并发代码。

以上就是Java中AtomicInteger原子类使用方法的详细内容,更多请关注资源网其它相关文章!

相关标签: java 操作系统 工具 栈 ai 热点 并发编程 性能瓶颈 数据丢失 无锁 为什么 有锁 Java count 子类 整型 int 循环 数据结构 栈 线程 多线程 并发 事件 算法 性能优化 大家都在看: Java中Octet类加法操作的实现与二进制处理 Java中自定义8位二进制数类Octet的加法实现教程 Java匿名内部类在字节码中的命名解析 Java教程:如何扁平化嵌套ArrayList并将其元素填充到数组中 在Java中使用try catch块的正确方法

标签:  java 操作系统 工具  ai 热点 并发编程 性能瓶颈 数据丢失 无锁 为什么 有锁 Java count 子类 整型 int 循环 数据结构 线程 

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。