并发编程原理与实战(三十)原子操作进阶,原子数组与字段更新器精讲
上两篇文章学习了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 guaranteesthis.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
多次运行,从运行结果看,两种方式的耗时差不多,从耗时上并不能明显的区分性能差异。