AtomicReferenceArray源码解析

本文详细解析了java.util.concurrent.atomic包下的AtomicReferenceArray类,探讨了其底层引用数组的原子读写操作,以及如何通过泛型支持多种数据类型的高级原子操作。文章深入分析了源码,包括变量初始化、构造方法、关键方法实现,以及与AtomicIntegerArray的主要区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

    AtomicReferenceArray类是java.util.concurrent.atomic包下的提供了可以原子读取和写入的底层引用数组的操作,并且还包含高级原子操作。 AtomicReferenceArray支持对底层引用数组变量的原子操作。 利用泛型机制可以对多种数据类型进行操作,如Integer等。

二、源码解析

继承了哪些类

在这里插入图片描述
    由上图可知AtomicReferenceArray只继承了一个Serializable接口,其他什么也没有继承。

变量说明

//序列化序号,用来对对象进行反序列化时的判断
private static final long serialVersionUID = -6209656149925076980L;
//unsafe常量,设置为使用Unsafe.compareAndSwapInt进行更新,方法中多处需要使用unsafe变量
private static final Unsafe unsafe;
//获取数组中第一个元素的内存地址
private static final int base;
//shift:转变,在这里表示左移,因为这个是基于对象的数组,所以shift是根据不同对象来决定不同值的
private static final int shift;
//获取 array 相对于对象的偏移量
private static final long arrayFieldOffset;
//存放元素的数组
private final Object[] array; // must have exact type Object[]

shift是如何获取的?

static {
    try {
        //获取unsafe的对象
        unsafe = Unsafe.getUnsafe();
        //获取 array 相对于对象的偏移量,利用unsafe对象
        arrayFieldOffset = unsafe.objectFieldOffset
            (AtomicReferenceArray.class.getDeclaredField("array"));
        //利用unsafe对象获取数组中第一个元素的内存地址
        base = unsafe.arrayBaseOffset(Object[].class);
        //获取数据中单个元素占用的字节数
        int scale = unsafe.arrayIndexScale(Object[].class);
        //numberOfLeadingZeros表示将scale变成二进制位的话最左边有多少个0
        //numberOfLeadingZeros(scale)则为00000000 00000000 00000000 00000100
        //shift = 31 - 29 = 2,即左移2位,如果对象是Integer
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    } catch (Exception e) {
        throw new Error(e);
    }
}

    注意:AtomicReferenceArray中的变量unsafe,base是在static中再创建实例对象的,这是和AtomicIntegerArray等不同的地方之一。

构造方法

    AtomicReferenceArray类的构造方法有两个,一个是自己根据参数length来new一个长度为length的数组,另外一个根据传入的数组参数将此参数数组赋值给变量array。

//构造方法创建长度为length的数组,并且赋值给数组元素array
public AtomicReferenceArray(int length) {
    array = new Object[length];
}

//将参数数组赋值给元素变量数组
public AtomicReferenceArray(E[] array) {
    // Visibility guaranteed by final field guarantees
    this.array = Arrays.copyOf(array, array.length, Object[].class);
}

两个特殊的方法

//检查下标i是否越界,并且返回其在内存中从base开始的偏移量
private long checkedByteOffset(int i) {
    //检查数组下标是否越界
    if (i < 0 || i >= array.length)
        throw new IndexOutOfBoundsException("index " + i);
    return byteOffset(i);
}

//返回下标i在内存中从base开始数的内存地址偏移量
private static long byteOffset(int i) {
    //比如i为2,从上可知shift为2,则 i << shift就是2*4,不过由于是泛型,这里shift一开始是不确定的
    return ((long) i << shift) + base;
}

    AtomicReferenceArray中大多数方法获取元素都是靠checkedByteOffset(int i) 方法来进行获取的,而checkedByteOffset(int i)又是靠byteOffset(int i)来实现的。

其他方法

① get(int i)方法。

//获取下标为i的元素
public final E get(int i) {
    //checkedByteOffset(i)表示检查下标是否越界并且返回下标i的从base开始的内存地址
    //getRaw(offset)方法就是根据内存偏移量来获取指定内存地址的元素
    return getRaw(checkedByteOffset(i));
}

//根据内存地址的偏移量获取数组的元素
private E getRaw(long offset) {
    return (E) unsafe.getObjectVolatile(array, offset);
}

② set(int i, E newValue)方法。

//插入新的值到指定数组下标的位置
public final void set(int i, E newValue) {
    unsafe.putObjectVolatile(array, checkedByteOffset(i), newValue);
}

③ getAndSet(int i, E newValue)方法。

//根据下标获取数组中的元素的值,并且将此数组的下标的值变为新值newValue
public final E getAndSet(int i, E newValue) {
    return (E)unsafe.getAndSetObject(array, checkedByteOffset(i), newValue);
}

④ compareAndSet(int i, E expect, E update)方法。

//根据数组下标获取数组元素,然后和expect进行比较,若一样,就将其值替换为update
//就是CAS的原理
public final boolean compareAndSet(int i, E expect, E update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

//根据偏移量offset获取其内存地址的值,并且和expect进行比较,若相等就将其值替换为update
private boolean compareAndSetRaw(long offset, E expect, E update) {
    //compareAndSetRaw就是根据内存地址的偏移量中的值来与expect值进行比较
    return unsafe.compareAndSwapObject(array, offset, expect, update);
}

⑤ toString()方法。

//toString方法是利用StringBuilder来进行处理的
public String toString() {
    //若数组为空,则length为0
    int iMax = array.length - 1;
    //若数组为空,则0 - 1 = -1就返回“[]if (iMax == -1)
        return "[]";

    StringBuilder b = new StringBuilder();
    b.append('[');
    //遍历数组元素,并且添加到StringBuilder中
    for (int i = 0; ; i++) {
        b.append(getRaw(byteOffset(i)));
        //条件判断,用来跳出此循环并且返回结果
        if (i == iMax)
            return b.append(']').toString();
        b.append(',').append(' ');
    }
}

AtomicReferenceArray和AtomicIntegerArray等的区别

  1. AtomicReferenceArray的变量unsafe和base是在static中创建实例对象的,而AtomicIntegerArray是在变量声明中就以及创建实例了。
  2. AtomicReferenceArray比AtomicIntegerArray多了一个arrayFieldOffset变量
  3. AtomicReferenceArray中的方法没有AtomicIntegerArray中的getAndIncrement或 incrementAndGet等方法。
  4. AtomicIntegerArray等的shift变量是确定的(2或者3),而AtomicReferenceArray由于是利用泛型,即在声明之前都不知道其shift值。

三、总结

    AtomicReferenceArray和AtomicIntegerArray等其实还是差不多的,代码上的区别就是多了arrayFieldOffset变量以及少了几个方法,还有其数据类型的不同而已。其实只要理解AtomicIntegerArray,那么AtomicReferenceArray也不难理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值