Java原子类之AtomicIntegerArray源码分析
一、简介
AtomicIntegerArray
可以用原子方式操作其元素的int
数组,是对Integer
数组支持的原子性操作。原子数组类与对应的普通原子类相比,只是多了通过索引找到内存中元素地址的操作而已。
注意:原子数组并不是说可以让线程以原子方式一次性地操作数组中所有元素的数组,而是指对于数组中的每个元素,可以以原子方式进行操作。
说得简单点,原子数组类型其实可以看成原子类型组成的数组 。
比如:
AtomicIntegerArray array = new AtomicIntegerArray(10);
array.getAndIncrement(0); // 将第0个元素原子地增加1
等同于
AtomicInteger[] array = new AtomicInteger[10];
array[0].getAndIncrement(); // 将第0个元素原子地增加1
二、案例
class AtomicIntArrayDemo {
public static void main(String[] args) {
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{45, 23, 13, 47, 12, 42});
for (int i = 0; i < array.length(); i++) {
final int j = i;
new Thread(() -> {
array.compareAndSet(j, 13, 31);
array.getAndAdd(j, 2);
array.decrementAndGet(j);
array.getAndSet(j, array.get(j) - 1);
}).start();
}
System.out.println(array);
}
}
三、源码分析
3.1 构造方法和主要属性
AtomicIntegerArray
其实和其它原子类区别并不大,只不过构造的时候传入的是一个int[]
数组,然后底层通过Unsafe
类操作数组:
public class AtomicIntegerArray implements java.io.Serializable {
private static final long serialVersionUID = 2862133569453604235L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取数组起始位置的偏移量
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
static {
// 获取数据元素的大小(size),int类型的是 4
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// numberOfLeadingZeros:返回无符号整型i的最高非零位前面的0的个数,int类型(4)的前置0个数为29
// 下面表达式返回位移量,用于计算下标。用于<<操作符,表示乘以2^shift 2^2=4
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
//计算第i个元素的偏移量
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
public AtomicIntegerArray(int length) {
array = new int[length];
}
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
}
可以看到,AtomicIntegerArray
提供了两种构造器,本质都是内部利用array
变量保存一个int[]
数组引用。另外,AtomicIntegerArray
利用Unsafe
类直接操作int[]
对象的内存地址,以达到操作数组元素的目的,几个关键的变量解释如下:
int base = unsafe.arrayBaseOffset(int[].class);
Unsafe
类的arraBaseOffset
方法:返回指定类型数组的第一个元素地址相对于数组起始地址的偏移值。
int scale = unsafe.arrayIndexScale(int[].class);
Unsafe
类的arrayIndexScale
方法:返回指定类型数组的元素所占用的字节数。比如int[]
数组中的每个int
元素占用4
个字节,就返回4
。
那么,通过base + i * sacle
其实就可以知道索引i的元素在数组中的内存起始地址。但是,观察AtomicIntegerArray的byteOffset方法,是通过i << shift + base
的公式计算元素的起始地址的:
这里,
其实就等于scale 。
shift = 31 - Integer.numberOfLeadingZeros(scale)
,Integer.numberOfLeadingZeros(scale)
是将scale转换为2进制,然后从左往右数连续0的个数。
读者可以自己计算下:
shift = 31 - Integer.numberOfLeadingZeros(4) = 31 - 29 =2
之所以要这么绕一圈,其实是处于性能的考虑,通过移位计算乘法的效率往往更高。
3.2 获取指定下标元素 - get方法
get()
方法主要利用byteOffset
根据公式offset = base + i * scale
求出数组对象的起始位置下标元素的偏移量offset
,然后利用unsafe.getIntVolatile
根据公式indexAddr = arrayAddr + offset
求出出指定偏移位置的元素值
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
// 下标检查,并计算数组对象起始位置至此下标元素的偏移量
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
// 计算偏移量
private static long byteOffset(int i) {
return ((long) i << shift) + base; // i*2^shift +base即,"i*元素间距+对象头长度"
}
private int getRaw(long offset) {
// 利用unsafe方法求出指定偏移位置的int值
return unsafe.getIntVolatile(array, offset);
}
3.2 更新指定下标元素 - set方法
set
方法与get
方法类似,都要先调用checkedByteOffset
计算偏移量offset
,不过此set
方法要调用putIntVolatile
在指定内存位置修改值而已。
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
3.3 getAndSet方法
先获取值再设置新值,同样先调用checkedByteOffset
计算偏移量offset
,然后getAndSetInt
进行CAS
自旋更新指定的元素。
public final int getAndSet(int i, int newValue) {
return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}
// unsafe的方法
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
3.4 compareAndSet方法
CAS
更新期望的元素,先调用checkedByteOffset
计算偏移量offset
,然后调用compareAndSwapInt
尝试CAS
更新元素。
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(array, offset, expect, update);
}
四、AtomicIntegerArray的兄弟类
除AtomicIntegerArray
外,JDK
提供了AtomicLongArray
(长整型类型数组)、AtomicReferenceArray
(引用类型数组)两种类型的原子数组。AtomicLongArray
和AtomicReferenceArray
的使用和源码与AtomicIntegerArray
大同小异,具体请参考Oracle官方文档和源码。
AtomicIntegerArray
对应AtomicInteger
,AtomicLongArray
对应AtomicLong
,AtomicReferenceArray
对应AtomicReference
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器