当前位置: 首页 > news >正文

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

上两篇文章学习了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

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

http://www.dtcms.com/a/389664.html

相关文章:

  • 前端-详解Vue异步更新
  • 基于风格的对抗生成网络
  • 【JavaScript】SSE
  • JAVA算法练习题day15
  • 线性表---双链表概述及应用
  • 作业帮前端面试(准备)
  • 51单片机-使用单总线通信协议驱动DS18B20模块教程
  • 全文单侧引号的替换方式
  • NVIDIA RTX4090 在Ubuntu系统中开启P2P peer access 直连访问
  • 再次深入学习深度学习|花书笔记2
  • 中移物联ML307C模组OPENCPU笔记1
  • 计算机视觉
  • VScode实现uniapp小程序开发(含小程序运行、热重载等)
  • Redis的各种key问题
  • 元宇宙与医疗产业:数字孪生赋能医疗全链路革新
  • 为你的数据选择合适的分布:8个实用的概率分布应用场景和选择指南
  • 掌握Stable Diffusion WebUI:模型选择、扩展管理与部署优化
  • LVGL拼音输入法优化(无bug)
  • 多层感知机:从感知机到深度学习的关键一步
  • PostgreSQL绿色版整合PostGIS插件,以Windows 64位系统为例
  • GEO优化推荐案例:2025年上海源易信息科技的全链路实践
  • 时空预测论文分享:多模态融合 空间索引结构 超图 时演化因果关系
  • 智能手机产量增长4%
  • MySQL高可用MHA实战指南
  • Coze源码分析-资源库-创建工作流-后端源码-核心技术/总结
  • 《棒球团建》国家级运动健将·棒球1号位
  • 基于STM32单片机生理监控心率脉搏TFT彩屏波形曲线加体温测量
  • Selenium 浏览器自动化完全指南:从环境搭建到实战应用
  • C51单片机——开发学习:中断
  • 树与二叉树【数据结构】