通过原子的方式更新数组中的某个元素,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上所占的空间是一样大小的;另一种是引用类型,引用类型的成员变量一般是保存一个指向这个实例的一个指针,指针的大小是固定的,所以,相同类的实例所占的内存大小是固定相等的】