前言
原子更新数组类顾名思义,通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素。
以上3个类提供的方法几乎一模一样,以 AtomicIntegerArray 类为例,它主要是提供原子的方式更新数组里的整型,其常用方法如下:
// 创建给定长度的新 AtomicIntegerArray。 AtomicIntegerArray(int length) // 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。 AtomicIntegerArray(int[] array) /** * 返回值与函数名字相关,比如get在前就返回原始值再进行加减设置等操作,get在后则先进行操作再返回更新后的值 * */ // 以原子方式将给定值添加到索引 i (从0开始)的元素。 int addAndGet(int i, int delta) // 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 boolean compareAndSet(int i, int expect, int update) // 以原子方式将索引 i 的元素减1。 int decrementAndGet(int i) // 获取位置 i 的当前值。 int get(int i) // 以原子方式将给定值与索引 i 的元素相加。 int getAndAdd(int i, int delta) // 以原子方式将索引 i 的元素减 1。 int getAndDecrement(int i) // 以原子方式将索引 i 的元素加 1。 int getAndIncrement(int i) // 以原子方式将位置 i 的元素设置为给定值,并返回旧值。 int getAndSet(int i, int newValue) // 以原子方式将索引 i 的元素加1。 int incrementAndGet(int i) // 最终将位置 i 的元素设置为给定值。 void lazySet(int i, int newValue) // 返回该数组的长度。 int length() // 将位置 i 的元素设置为给定值。 void set(int i, int newValue) // 返回数组当前值的字符串表示形式。 String toString() // 如果位置 i 的元素当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 boolean weakCompareAndSet(int i, int expect, int update)
AtomicIntegerArray 的使用例子:
1 import java.util.concurrent.atomic.AtomicIntegerArray; 2 3 public class Demo 4 { 5 static AtomicIntegerArray atom = new AtomicIntegerArray(a); 6 public static void main(String[] agrs) 7 { 8 int[] a = {1, 2, 3, 4, 5}; 9 10 System.out.println("原始数组:" + atom); 11 12 System.out.println("调用addAndGet(1, 9)方法返回值:" + atom.addAndGet(1, 9)); 13 System.out.println("调用后数组为:" + atom); 14 15 System.out.println("调用getAndDecrement(2)方法返回值:" + atom.getAndDecrement(2)); 16 System.out.println("调用后数组为:" + atom); 17 18 System.out.println("调用incrementAndGet(3)方法返回值:" + atom.incrementAndGet(3)); 19 System.out.println("调用后数组为:" + atom); 20 21 System.out.println("调用compareAndSet(4, 5, 100)方法返回值:" + atom.compareAndSet(4, 5, 100)); 22 System.out.println("调用后数组为:" + atom); 23 } 24 }
打印结果为:
原始数组:[1, 2, 3, 4, 5] 调用addAndGet(1, 9)方法返回值:11 调用后数组为:[1, 11, 3, 4, 5] 调用getAndDecrement(2)方法返回值:3 调用后数组为:[1, 11, 2, 4, 5] 调用incrementAndGet(3)方法返回值:5 调用后数组为:[1, 11, 2, 5, 5] 调用compareAndSet(4, 5, 100)方法返回值:tr 调用后数组为:[1, 11, 2, 5, 100]
AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。
源码如下:
1 /** 2 * Creates a new AtomicLongArray of the given length, with all 3 * elements initially zero. 4 * 5 * @param length the length of the array 6 */ 7 public AtomicLongArray(int length) { 8 array = new long[length]; 9 } 10 11 /** 12 * Creates a new AtomicLongArray with the same length as, and 13 * all elements copied from, the given array. 14 * 15 * @param array the array to copy elements from 16 * @throws NullPointerException if array is null 17 */ 18 public AtomicLongArray(long[] array) { 19 // Visibility guaranteed by final field guarantees 20 this.array = array.clone(); 21 }
那这个类是如何做到原子性操作的呢?我们可以通过底层代码来看一下
下面仅以incrementAndGet()为例,对AtomicInteger的原理进行说明。
1 public final int incrementAndGet(int i) { 2 return addAndGet(i, 1); 3 } 4 public int addAndGet(int i, int delta) { 5 // 检查数组是否越界 6 int offset = checkedByteOffset(i); 7 while (true) { 8 // 获取int型数组的索引 offset 的原始值 9 int current = getRaw(offset); 10 // 修改int型值 11 int next = current + delta; 12 // 通过CAS更新int型数组的索引 offset的值。 13 if (compareAndSetRaw(offset, current, next)) 14 return next; 15 } 16 }
incrementAndGet() 其实调用了 addAndGet() 方法,指定相加的数为1。在循环中,获取下表为 i 的元素的当前值,通过 compareAndSet() 方法与下标为 i 的元素的原始值进行对比,如果相同则进行更新,否则循环再执行一遍。
其中 getRaw() 和 compareAndSet() 方法都是调用了 Unsafe 类中的方法
private int getRaw(int offset) { return unsafe.getIntVolatile(array, offset); } private boolean compareAndSetRaw(int offset, int expect, int update) { return unsafe.compareAndSwapInt(array, offset, expect, update); }
private static final Unsafe unsafe = Unsafe.getUnsafe();
Unsafe 这个类提供了硬件级别的原子操作,因为Java不能直接访问操作系统的底层,而只能通过本地方法(native)来访问,Unsafe 类中就提供了一些这样的本地方法。这些方法可以直接定位对象某字段的内存位置,也可对它进行修改。上面两个方法就是这样的。
参考资料:
Java多线程系列–“JUC原子类”03之 AtomicLongArray原子类