上两篇文章学习了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
多次运行,从运行结果看,两种方式的耗时差不多,从耗时上并不能明显的区分性能差异。
752

被折叠的 条评论
为什么被折叠?



