原子更新数组以及相关源码

本文探讨了Java中如何使用AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray进行原子更新数组元素。以AtomicIntegerArray为例,详细介绍了addAndGet、compareAndSet等方法,并解释了为何在原子数组操作中使用final而非volatile,关键在于UNSAFE类的byteOffset和compareAndSwapInt方法。同时,文章提及AtomicReferenceArray的内存占用规律。
摘要由CSDN通过智能技术生成

通过原子的方式更新数组中的某个元素,J.U.C.Atomic中提供了以下三个类:

  • AtomicIntegerArray:原子更新整形数组中的元素
  • AtomicLongArray:原子更新长整形数组中的元素
  • AtomicReferenceArray:原子更新引用类型数组中的元素

接下来以AtomicIntegerArray为例,讲述jdk1.8中的主要代码:

  • int addAndGet(int i, int delta):原子的方式将输入值与数组中索引i的元素相加,返回相加后的值,可以通过值来判断是否操作成功
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则将当前值以原子方式将数组位置i设置成update值

这是其中提供函数操作数组中的值的方法【利用函数名区分记忆】

  • int getAndUpdate(int i, IntUnaryOperator updateFunction):返回操作之前的值
  • int updateAndGet(int i, IntUnaryOperator updateFunction):返回操作之后的值
  • int getAndAccumulate(int i, int x,IntBinaryOperator accumulatorFunction):返回操作之前的值
  • int accumulateAndGet(int i, int x,IntBinaryOperator accumulatorFunction):返回操作之后的值
private final int[] array;//利用数组保存数据
public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
    long offset = checkedByteOffset(i);
    int prev, next;
    do {
        prev = getRaw(offset);//获取array[i]的值
        next = updateFunction.applyAsInt(prev);//使用函数之后的值
    } while (!compareAndSetRaw(offset, prev, next));//cas的方式更改值,不成功就继续循环,知道修改成功为止
    return next;
}
//检查下标的合法性,下标需要在数组的范围内,否则抛出异常
private long checkedByteOffset(int i) {
    if (i < 0 || i >= array.length)
        throw new IndexOutOfBoundsException("index " + i);
    return byteOffset(i);
}

使用方式:

public class AtomicIntegerArrayTest {
    public static void main(String[] args){
        // 通过设置数组容量的方式初始化AtomicIntegerArray
        AtomicIntegerArray aiArray = new AtomicIntegerArray(5);
        for(int i=0;i<5;i++){
            aiArray.set(i,i+1);
        }
        for(int i=0;i<5;i++){
            System.out.println(aiArray.get(i));
        }
        // 通过设置数组的方式初始化AtomicIntegerArray
        // 注意这里是将数组复制到AtomicIntegerArray中,修改不影响
        int[] arr = {4,3,2,1};
        aiArray = new AtomicIntegerArray(arr);
        aiArray.set(0,9);
        System.out.println(aiArray.get(0)); //9
        System.out.println(arr[0]);  //4

    }
}

发现一个问题,在原子操作基本类型中,基本类型使用volatile修饰,使用cas的方式更新,以此保证线程之间操作的正确性,但是在原子操作数组中,却是使用final修饰保存的变量,有点好奇原因,去探寻了一下发现,主要原因在UNSAFE类上,这是一个由C/C++实现其中所有方法的类,主要是实现一些原子操作,基本上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);
}

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;
}

使用byteOffset方法计算偏移量,然后利用unsafe.compareAndSwapInt方法完成操作。

【题外话:由于AtomicReferenceArray类也是同样的实现方法,所以可以推测相同的类的实例所占的空间是固定的。将类的成员变量分成两种,一种是基础类型,在jvm上所占的空间是一样大小的;另一种是引用类型,引用类型的成员变量一般是保存一个指向这个实例的一个指针,指针的大小是固定的,所以,相同类的实例所占的内存大小是固定相等的】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值