并发编程原理与实战(三十)原子操作进阶,原子数组与字段更新器精讲

上两篇文章学习了int、long、boolean三种基本数据类型以及引用数据类型对应的原子类。JDK中对这几种数据类型的数组提供了相应的原子类,本文就来学习数组的原子类以及原子字段更新器。

原子数组引入目的

无锁化数组元素操作‌。原子数组提供对数组元素的原子读写能力,避免了对整个数组加锁的开销,特别适用于多线程并发修改数组不同元素的场景。例如不同线程可安全操作数组的不同索引位置。

传统同步锁(如synchronized)会锁住整个数组对象,而原子数组通过CAS机制实现元素级别的原子性,显著提升并发性能。

原子数组内部通过volatile语义和内存屏障保证修改后的值对其他线程立即可见,解决了普通数组在多线程下的可见性问题。

AtomicIntegerArray核心API

构造函数

(1)AtomicIntegerArray(int length)

/**
 * Creates a new AtomicIntegerArray of the given length, with all
 * elements initially zero.
 *
 * @param length the length of the array
 */
public AtomicIntegerArray(int length) {
    array = new int[length];
}

该构造函数传入数组的长度创建一个AtomicIntegerArray对象,数组的元素初始化为0。

(2)AtomicIntegerArray(int[] array)

/**
 * Creates a new AtomicIntegerArray with the same length as, and
 * all elements copied from, the given array.
 *
 * @param array the array to copy elements from
 * @throws NullPointerException if array is null
 */
public AtomicIntegerArray(int[] array) {
    // Visibility guaranteed by final field guarantees
    this.array = array.clone();
}

该构造函数传入一个int类型的数组创建一个AtomicIntegerArray对象,长度同传入的数组长度,数组元素的值为传入的数组的元素的拷贝。

基础原子操作

(1)int get(int i)

/**
 * Returns the current value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getVolatile}.
 *
 * @param i the index
 * @return the current value
 */
public final int get(int i) {
    return (int)AA.getVolatile(array, i);
}

获取原子数组指定索引i处的当前值,具有volatile读的内存语义,保证线程间可见性。

(2)set(int i, int newValue)‌

/**
 * Sets the element at index {@code i} to {@code newValue},
 * with memory effects as specified by {@link VarHandle#setVolatile}.
 *
 * @param i the index
 * @param newValue the new value
 */
public final void set(int i, int newValue) {
    AA.setVolatile(array, i, newValue);
}

直接设置原子数组指定索引的值为newValue,具有volatile写的内存语义,写入后对其他线程立即可见。

复合原子操作

(1)compareAndSet(int i, int expect, int update)‌

/**
 * Atomically sets the element at index {@code i} to {@code
 * newValue} if the element's current value {@code == expectedValue},
 * with memory effects as specified by {@link VarHandle#compareAndSet}.
 *
 * @param i the index
 * @param expectedValue the expected value
 * @param newValue the new value
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(int i, int expectedValue, int newValue) {
    return AA.compareAndSet(array, i, expectedValue, newValue);
}

核心CAS操作:当索引i处的值等于expect时,原子性地更新为newValue,返回是否成功。

(2)weakCompareAndSet(int i, int expect, int update)‌

/**
 * Possibly atomically sets the element at index {@code i} to
 * {@code newValue} if the element's current value {@code == expectedValue},
 * with memory effects as specified by {@link VarHandle#weakCompareAndSetPlain}.
 *
 * @param i the index
 * @param expectedValue the expected value
 * @param newValue the new value
 * @return {@code true} if successful
 * @since 9
 */
public final boolean weakCompareAndSetPlain(int i, int expectedValue, int newValue) {
    return AA.weakCompareAndSetPlain(array, i, expectedValue, newValue);
}

该方法可能会意外失败且不提供顺序保证,因此仅在某些特殊情况下可作为compareAndSet的替代方案‌。

(3)getAndSet(int i, int newValue)

/**
 * Atomically sets the element at index {@code i} to {@code
 * newValue} and returns the old value,
 * with memory effects as specified by {@link VarHandle#getAndSet}.
 *
 * @param i the index
 * @param newValue the new value
 * @return the previous value
 */
public final int getAndSet(int i, int newValue) {
    return (int)AA.getAndSet(array, i, newValue);
}

该方法原子性地设置指定索引位置i的元素为新值newValue,并返回旧值,常用于交换场景。

数值原子运算

(1)getAndIncrement(int i)/getAndDecrement(int i)

/**
 * Atomically increments the value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * <p>Equivalent to {@code getAndAdd(i, 1)}.
 *
 * @param i the index
 * @return the previous value
 */
public final int getAndIncrement(int i) {
    return (int)AA.getAndAdd(array, i, 1);
}

/**
 * Atomically decrements the value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * <p>Equivalent to {@code getAndAdd(i, -1)}.
 *
 * @param i the index
 * @return the previous value
 */
public final int getAndDecrement(int i) {
    return (int)AA.getAndAdd(array, i, -1);
}

这两个方法对指定索引i的值原子递增/递减1,返回旧值。

(2)incrementAndGet(int i)/decrementAndGet(int i)‌

/**
 * Atomically increments the value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * <p>Equivalent to {@code addAndGet(i, 1)}.
 *
 * @param i the index
 * @return the updated value
 */
public final int incrementAndGet(int i) {
    return (int)AA.getAndAdd(array, i, 1) + 1;
}

/**
 * Atomically decrements the value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * <p>Equivalent to {@code addAndGet(i, -1)}.
 *
 * @param i the index
 * @return the updated value
 */
public final int decrementAndGet(int i) {
    return (int)AA.getAndAdd(array, i, -1) - 1;
}

这两个方法先对指定索引i的值原子性的递增/递减1,再返回新值。

(3)getAndAdd(int i, int delta)‌

/**
 * Atomically adds the given value to the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getAndAdd}.
 *
 * @param i the index
 * @param delta the value to add
 * @return the previous value
 */
public final int getAndAdd(int i, int delta) {
    return (int)AA.getAndAdd(array, i, delta);
}

该方法对指定索引i的值原子性地增加delta值,返回旧值。

(4)length()‌

/**
 * Returns the length of the array.
 *
 * @return the length of the array
 */
public final int length() {
    return array.length;
}

该方法返回原子数组的长度。

典型例子

下面通过一个例子对比AtomicIntegerArray与synchronized锁住整个数组的性能差异。

需求:用10个线程并发操作长度为1000的数组,每个线程对其中的随机索引位置进行10000次递增操作。

(1) 使用synchronized实现

public class SyncArrayDemo {
    //数组长度
    private static final int ARRAY_SIZE = 1000;
    //线程数量
    private static final int THREADS = 10;
    //每个线程对数组元素递增操作次数
    private static final int OPS = 10000;

    //数组
    private static int[] syncArray = new int[ARRAY_SIZE];
    //锁对象
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS];
        long start = System.currentTimeMillis();

        for (int i = 0; i < THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPS; j++) {
                    int idx = (int) (Math.random() * ARRAY_SIZE);
                    // 操作数组元素时加锁
                    synchronized (lock) {
                        syncArray[idx]++;
                    }
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();
        System.out.println("Synchronized耗时: " + (System.currentTimeMillis() - start) + "ms");
    }
}

运行结果

Synchronized耗时: 199ms

(2)使用AtomicIntegerArray实现

public class AtomicArrayDemo {
    //数组长度
    private static final int ARRAY_SIZE = 1000;
    //线程数量
    private static final int THREADS = 10;
    //每个线程对数组元素递增操作次数
    private static final int OPS = 10000;

    //原子数组
    private static AtomicIntegerArray atomicArray = new AtomicIntegerArray(ARRAY_SIZE);

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS];
        long start = System.currentTimeMillis();

        for (int i = 0; i < THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPS; j++) {
                    int idx = (int) (Math.random() * ARRAY_SIZE);
                    // 无锁原子递增
                    atomicArray.getAndIncrement(idx);
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) t.join();
        System.out.println("Atomic耗时: " + (System.currentTimeMillis() - start) + "ms");
    }
}

运行结果

Atomic耗时: 83ms

从运行结果可以看出,使用AtomicIntegerArray实现并发修改数组元素时耗时从199ms减少到83ms,性能提升明显。

原子字段更新器引入目的

字段更新器(如AtomicIntegerFieldUpdater)允许直接对对象的volatile字段进行原子操作,无需将字段包装为AtomicInteger等原子类,节省内存开销。

当需要为第三方库或遗留代码中的volatile字段添加原子操作时,字段更新器无需修改原有类结构即可实现线程安全,避免重构成本。

相比直接使用原子类,字段更新器通过反射和偏移量计算直接操作对象字段,减少了对象创建和内存访问层级,适合高频更新的场景。

AtomicIntegerFieldUpdater核心API

创建实例

原子字段更新器并没有提供公有的构造函数来创建实例,而是通过一个静态的newUpdater()方法来创建实例。

/**
 * Creates and returns an updater for objects with the given field.
 * The Class argument is needed to check that reflective types and
 * generic types match.
 *
 * @param tclass the class of the objects holding the field
 * @param fieldName the name of the field to be updated
 * @param <U> the type of instances of tclass
 * @return the updater
 * @throws IllegalArgumentException if the field is not a
 * volatile integer type
 * @throws RuntimeException with a nested reflection-based
 * exception if the class does not hold field or is the wrong type,
 * or the field is inaccessible to the caller according to Java language
 * access control
 */
@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                          String fieldName) {
    return new AtomicIntegerFieldUpdaterImpl<U>
        (tclass, fieldName, Reflection.getCallerClass());
}

该方法创建指定类的字段更新器实例,需要Class参数来校验反射类型与泛型类型是否匹配。

  • @param tclass 包含该字段的对象的类
  • @param fieldName 待更新字段的名称
  • @param tclass实例的类型
  • @return 字段更新器实例
  • @throws IllegalArgumentException 如果字段不是volatile整型
  • @throws RuntimeException 若类不包含该字段、字段类型不匹配

需要特别注意的是,要更新的类的字段必须是volatile修饰,否则将会抛出IllegalArgumentException异常。

其他方法

原子字段更新器同样提供了基础CAS操作、 ‌数值原子运算‌这些方法,和之前其他原子类的方法类似,在此举例一下,不再详细分析。

  • boolean compareAndSet(T obj, int expect, int update)//基础CAS操作
  • int getAndIncrement(T obj)// 原子递增,返回旧值
  • int incrementAndGet(T obj)// 原子递增,返回新值
  • int getAndAdd(T obj, int delta)// 原子加delta,返回旧值
  • int addAndGet(T obj, int delta)// 原子加delta,返回新值
  • int get(T obj) //直接取值
  • void set(T obj, int newValue) //直接设值

典型使用示例‌

下面通过一个例子来对比下使用AtomicIntegerFieldUpdater字段更新器更新字段自增和使用AtomicInteger 自增时的耗时对比。

public class AtomicBenchmark {
    //要原子更新字段的类
    private static class Target {
        volatile int field;
        AtomicInteger atomic = new AtomicInteger();
    }

    //原子字段更新器
    static final AtomicIntegerFieldUpdater<Target> updater =
            AtomicIntegerFieldUpdater.newUpdater(Target.class, "field");
    static final int opt = 100000000;

    public static void main(String[] args) {
        Target t = new Target();

        // AtomicIntegerFieldUpdater测试
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < opt; i++) {
            updater.incrementAndGet(t);
        }
        long duration1 = System.currentTimeMillis() - start1;

        // AtomicInteger测试
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < opt; i++) {
            t.atomic.incrementAndGet();
        }
        long duration2 = System.currentTimeMillis() - start2;

        System.out.println("FieldUpdater耗时:" + duration1);
        System.out.println("AtomicInteger耗时:" + duration2);
    }
}

运行结果

FieldUpdater耗时:605
AtomicInteger耗时:636

多次运行,从运行结果看,两种方式的耗时差不多,从耗时上并不能明显的区分性能差异。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值