AtomicLongArray内部维护了一个int类型的数组,需要先复习下数组对象的在内存中的结构,这对接下来对数组类型原子类的理解至关重要。
一、数组对象的内存结构
我们运行以下代码并将数组对象的内存结构通过JOL工具打印出来,关于这部分知识,参考之前的文章:深入理解Java对象结构
public class ArrayTest {
public static void main(String[] args) {
//打印虚拟机信息
System.out.println(VM.current().details());
//十个元素的int数组
int[] array = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//打印内存结构
System.out.println(ClassLayout.parseInstance(array).toPrintable());
}
}
输出结果:
整个对象大小是16字节的对象头+40字节的对象体,一共56字节,由于正好是对齐字节数8的倍数,所以没有对齐字节。
问题来了,如果站在Unsafe类的角度上,如何实现快速访问数组中的某个元素?
Unsafe可以基于偏移量的快速访问,也就是说只要告诉Unsafe前边有多少个字节数,它就可以直接定位到需要访问的元素。如果我想访问第1个元素,那前边就只有对象头,那就是16字节;如果我想访问第二个元素,那就是16字节的对象头+4字节的第一个元素,就是20字节;如果我想访问呢第三个元素,那就是16字节的对象头+8字节的两个元素,就是24个字节。。。如果我想访问下标为i的元素,那就是(16+ix4)个字节。
我们知道位移运算要比乘法运算效率高的多,为了效率最大化,可以使用位移运算替代乘法运算,4正好是22满足位移计算替换的要求(热知识:将一个数i向左位移N位,实际上等效于ix2N,所以必须保证第二个乘数是2的幂次方),所以16+ix4可以替换为16+i<<2,没错,在数组类型原子类中,正是使用这种位移的方式快速定位元素的。
二、get方法源码解析
由于源代码比较长,分开一点一点来看,先看get方法:public final int get(int i)
,说起来你肯定不信,这个方法是这个类最复杂的方法了
public class AtomicIntegerArray {
//Unsafe类初始化
private static final Unsafe unsafe = Unsafe.getUnsafe();
//数组元素的内存偏移量
private static final int base = unsafe.arrayBaseOffset(int[].class);
//位移次数
private static final int shift;
//通过final保证可见性
private final int[] array;
static {
//确定数组中每个元素的大小,单位字节
int scale = unsafe.arrayIndexScale(int[].class);
//确定scale大小是2的幂次方
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);
}
//根据数组坐标查询内存偏移量
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
//获取某个元素的值
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
//调用unsafe方法根据偏移量获取某个方法的值
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
}
上面的整个源代码都是public final int get(int i)
方法相关的函数和变量。接下来逐步看下每段代码的意思。
静态代码块
static {
//确定数组中每个元素的大小,单位字节
int scale = unsafe.arrayIndexScale(int[].class);
//确定scale大小是2的幂次方
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
//确定位移次数,用于后续计算元素的内存偏移量
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
相关的注释在代码中,不再赘述,主要解答下几个疑问
- 为什么要验证scale大小是2的幂次方
- Integer.numberOfLeadingZeros(scale)的函数作用是什么
- 为什么要用31减去Integer.numberOfLeadingZeros(scale)
- 最终计算出来的shift值是干什么用的
相信第一次见这段代码的人都和我一样,对这段静态代码块中的每一行代码都有疑问
标签:AtomicLongArray,CAS,long,public,int,源码,scale,数组,final From: https://www.cnblogs.com/kuangdaoyizhimei/p/18440359