AtomicIntegerArray 源码解析(基于 JDK 1.8)
@
AtomicIntegerArray 可以原子的更新 int[] 中某个对象。
在找到数组第0个对象的偏移量之后,由于数组中每个对象是顺序排放的,可以根据对象大小计算出数组中某索引的偏移量,然后通过 Unsafe 相关的方法来获取或者修改。
1 偏移量的计算
假设数组中第 0 个对象的在数组中的偏移量为 x,每个对象的大小为 y,那么数组中第 i 个元素在数组中的偏移量为 x+i*y。下面是具体实现。
base 表示的就是第 0 个对象的偏移量 x,scale 或者 1<< shift 或者2shift 都表示每个对象的大小 y。
有几个点需要理解一下
-
如果 scale 是 2 的幂,则一定有 scale & (scale - 1)) == 0,这个是充要条件。
证明:a 表示 scale是2的幂,b表示 scale & (scale - 1)) == 0。
- a -> b:
假设scale是 ...00100... 则scale-1是 ...00011... 两个 & 得到 ...00000...
- b -> a:
用逆否命题,非a -> 非b: 如果scale不是2的幂,在大于0的情况下, 可以找到最高位的1,由于不是2的幂, 则最高位后面至少有一位是1 即, ...00100...1... -1 为 ...00100...0... 两个 & 得到 非零
-
2shift 就是 scale。
说明:scale 此时为 2 的幂,只有一个1,从高到低为 a个0,1个1, b个0。
其中 a+1+b =32。
由于 numberOfLeadingZeros 返回 a,可知 shift 是 b,此时 1<<shift 正好表示 scale。
public class AtomicIntegerArray implements java.io.Serializable {
private static final long serialVersionUID = 2862133569453604235L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
//base 表示初始位置 x
private static final int base = unsafe.arrayBaseOffset(int[].class);
//2^shift 等于 scale,表示每个对象的大小
private static final int shift;
private final int[] array;
static {
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
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 i*scale + base;
return ((long) i << shift) + base;
}
...
}
2 其他
首先介绍初始化,初始化有两种,一种是给定长度,生成新数组;另一种是给定数组,将数组设置为数组的深拷贝,防止不经过这个类修改而是直接修改外界数组,导致结果错误。
public AtomicIntegerArray(int length) {
array = new int[length];
}
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
其他的方法都使用 Unsafe,前面已经算出位置了,那就直接修改相应位置即可。有需要可以去看我的 Unsafe 讲解。
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {
long offset = checkedByteOffset(i);
int prev, next;
do {
prev = getRaw(offset);
next = updateFunction.applyAsInt(prev);
} while (!compareAndSetRaw(offset, prev, next));
return prev;
}
public final int getAndSet(int i, int newValue) {
return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}
public final void lazySet(int i, int newValue) {
unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
}
//其他方法省略
3 使用
import java.util.concurrent.atomic.AtomicIntegerArray;
public class A{
public static void main(String[] args) throws Exception {
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{10,20,30,40,50});
for (int i = 0; i <array.length() ; i++) {
int j=i;
new Thread(()->{
array.compareAndSet(j, 10, 30);
array.decrementAndGet(j);
array.getAndSet(j, array.get(j) - 1);
}).start();
}
Thread.sleep(2000);
System.out.println(array);
}
}
下面是我的公众号,Java与大数据进阶,分享 Java 与大数据笔面试干货,欢迎关注
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】博客园2025新款「AI繁忙」系列T恤上架,前往周边小店选购
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Android编译时动态插入代码原理与实践
· 解锁.NET 9性能优化黑科技:从内存管理到Web性能的最全指南
· 通过一个DEMO理解MCP(模型上下文协议)的生命周期
· MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题
· 记一次 .NET某固高运动卡测试 卡慢分析
· .net clr 8年才修复的BUG,你让我损失太多了
· 做Docx预览,一定要做这个神库!!
· 一个开源的 Blazor 跨平台入门级实战项目
· Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码
· 微信小程序/H5 调起确认收款界面