❃博主首页 :

「码到三十五」 ,同名公众号 :「码到三十五」

♝博主的话 :

搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基


在Java的并发编程中,AtomicIntegerFieldUpdater是一个非常重要的工具类,它提供了一种线程安全的方式来更新某个类的指定volatile int字段,而无需使用同步锁。

文章目录
  • 一、AtomicIntegerFieldUpdater简介
  • 二、AtomicIntegerFieldUpdater与AtomicInteger区别
  • 1. 使用场景
  • 2. 内存占用
  • 3. 实现机制
  • 二、使用AtomicIntegerFieldUpdater的步骤
  • 三、AtomicIntegerFieldUpdater的优势
  • AtomicIntegerFieldUpdater使用限制
  • 四、实际应用场景
  • 五、使用

一、AtomicIntegerFieldUpdater简介

AtomicIntegerFieldUpdater是Java并发包java.util.concurrent.atomic中的一个类,它利用反射机制,在不创建额外对象的情况下,能够原子地更新某个类的指定volatile int字段。这种机制特别适用于那些实例数量非常多,且每个实例都需要原子更新某个字段的场景,因为它可以显著减少内存占用并提高性能。

二、AtomicIntegerFieldUpdater与AtomicInteger区别

AtomicInteger可以保证内部的属性的操作时原子性的;AtomicIntegerFieldUpdater是保证其他类的属性的操作时原子性的。当一个类新建时,可以选型Atomic类型的原子类,但当对已经存在的一个类,如要保证内部的操作为原子性,就要借助AtomicIntegerFieldUpdater了。

但是AtomicIntegerFieldUpdater也有它的局限性,它处理的类中的属性必须保证是int类型的(不能是Integer包装类型)、必须是volatile类型的,否则不能用AtomicIntegerFieldUpdater来保证其原子性。

它们之间的主要区别还体现在使用场景、内存占用、以及实现机制上。

1. 使用场景
  • AtomicInteger:适用于单个变量需要原子性操作的场景。当你需要在多线程环境下安全地增加或减少一个整数值时,AtomicInteger是一个很好的选择。它封装了一个volatile int变量,并提供了多种原子操作方法,如incrementAndGet()decrementAndGet()等。
  • AtomicIntegerFieldUpdater:适用于类的实例数量较多,且每个实例都需要原子性地更新某个volatile int字段的场景。通过反射机制,AtomicIntegerFieldUpdater可以在不修改原有类结构的情况下,为类的指定字段提供原子更新能力。这种方式减少了为每个实例创建AtomicInteger对象的内存消耗,提高了性能。
2. 内存占用
  • AtomicInteger:每个需要原子性操作的变量都会创建一个AtomicInteger对象,因此在实例数量较多时,会占用较多的内存。
  • AtomicIntegerFieldUpdater:不需要为每个实例创建AtomicIntegerFieldUpdater对象,而是创建一个静态的AtomicIntegerFieldUpdater实例来更新所有实例的指定字段。这种方式显著减少了内存占用。
3. 实现机制
  • AtomicInteger:内部封装了一个volatile int变量,并通过CAS(Compare-And-Swap)操作来保证原子性。CAS是一种基于硬件的原子指令,它可以在不锁定对象的情况下,实现多个线程对同一个变量的安全访问和修改。
  • AtomicIntegerFieldUpdater:通过反射机制获取到类的指定字段的Field对象,然后利用sun.misc.Unsafe类的底层操作来实现CAS操作。Unsafe类提供了低级别的、非安全的、操作系统级别的访问方法,它可以直接访问内存、创建对象、数组等。由于AtomicIntegerFieldUpdater是基于反射和Unsafe类实现的,因此它只能更新公共字段或具有公共setter方法的字段。
二、使用AtomicIntegerFieldUpdater的步骤
  1. 字段声明
    被更新的字段必须是volatile int类型,这是使用AtomicIntegerFieldUpdater的前提条件。
  2. 创建Updater
    使用AtomicIntegerFieldUpdater.newUpdater()静态方法创建一个AtomicIntegerFieldUpdater实例。这个方法需要两个参数:一个是包含要更新字段的类的Class对象,另一个是字段的名称(String类型)。
  3. 原子更新
    通过Updater实例提供的方法(如compareAndSetgetAndIncrementgetAndSet等)进行原子更新操作。这些方法允许你在不锁定对象的情况下安全地更新字段。
三、AtomicIntegerFieldUpdater的优势
  1. 减少内存占用
    使用AtomicIntegerFieldUpdater可以避免为每个需要原子更新的字段创建一个AtomicInteger对象,从而显著减少内存占用。这在处理大量实例时尤为重要。
  2. 提高性能
    由于AtomicIntegerFieldUpdater直接操作底层字段,避免了通过方法调用间接访问字段的开销,因此在某些场景下,其性能可能优于使用AtomicInteger
  3. 灵活性
    AtomicIntegerFieldUpdater允许开发者在不改变现有类结构的情况下,为类中的某个字段提供原子更新能力。这种灵活性使得它非常适用于遗留代码的改造和优化。
AtomicIntegerFieldUpdater使用限制

(1)字段必须是volatile类型的,在线程之间共享变量时保证立即可见.eg:volatile int value = 3

(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(5)对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。

四、实际应用场景
  1. 缓冲区引用计数更新
    在Netty等网络编程框架中,缓冲区(如ByteBuf)通常使用引用计数来管理内存。由于缓冲区实例的数量可能非常大,使用AtomicIntegerFieldUpdater来更新引用计数可以避免为每个缓冲区实例创建一个AtomicInteger对象,从而显著减少内存占用。
  2. 并发数据结构
    在实现并发数据结构(如并发队列、并发哈希表等)时,AtomicIntegerFieldUpdater可以用于原子地更新数据结构的某些状态字段(如计数器、标记位等)。
  3. 性能监控
    在需要对系统的某些性能指标进行原子更新时(如请求计数器、活跃用户数等),AtomicIntegerFieldUpdater可以提供一个高效且线程安全的更新机制。
五、使用

假设有一个银行账户类BankAccount,其中包含一个volatile int类型的余额字段balance。我们希望在多线程环境中,能够安全地对这个余额字段进行增加操作,以确保在并发转账时余额的准确性。

首先,我们定义BankAccount类,并使用AtomicIntegerFieldUpdater来更新余额字段。

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class BankAccount {
    // 使用volatile修饰,保证内存可见性
    private volatile int balance;

    // 静态的AtomicIntegerFieldUpdater实例,用于更新balance字段
    private static final AtomicIntegerFieldUpdater<BankAccount> balanceUpdater =
            AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "balance");

    public BankAccount(int initialBalance) {
        this.balance = initialBalance;
    }

    // 使用AtomicIntegerFieldUpdater增加余额
    public void deposit(int amount) {
        balanceUpdater.addAndGet(this, amount);
    }

    // 获取当前余额
    public int getBalance() {
        return balance;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

接下来,我们创建一个多线程测试类来模拟多个账户同时存款的情况。

public class BankAccountTest {

    public static void main(String[] args) {
        // 创建一个账户,初始余额为100
        BankAccount account = new BankAccount(100);

        // 创建多个线程来模拟存款操作
        int numThreads = 10;
        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            final int amount = 50; // 每个线程存款50
            threads[i] = new Thread(() -> {
                account.deposit(amount);
                System.out.println("Thread " + Thread.currentThread().getName() + " deposited. New balance: " + account.getBalance());
            });
            threads[i].start();
        }

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

        // 输出最终余额
        System.out.println("Final balance: " + account.getBalance());
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

关注公众号[码到三十五]获取更多技术干货 !