Java
Java多线程

Java多线程 12 - AtomicLongArray详解

简介:AtomicLong是作用是对长整形进行原子操作,而AtomicLongArray的作用则是对长整形数组进行原子操作。

1. AtomicLongArray详解

AtomicLong是作用是对长整形进行原子操作,而AtomicLongArray的作用则是对长整形数组进行原子操作。

1.1. AtomicLongArray API列表

  • // 创建给定长度的新AtomicLongArray。
  • AtomicLongArray(int length)
  • // 创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素。
  • AtomicLongArray(long[] array)
  • // 以原子方式将给定值添加到索引i的元素。
  • long addAndGet(int i, long delta)
  • // 如果当前值等于预期值,则以原子方式将该值设置为给定的更新值。
  • boolean compareAndSet(int i, long expect, long update)
  • // 以原子方式将索引i的元素减1。
  • long decrementAndGet(int i)
  • // 获取位置i的当前值。
  • long get(int i)
  • // 以原子方式将给定值与索引i的元素相加。
  • long getAndAdd(int i, long delta)
  • // 以原子方式将索引i的元素减1。
  • long getAndDecrement(int i)
  • // 以原子方式将索引i的元素加1。
  • long getAndIncrement(int i)
  • // 以原子方式将位置i的元素设置为给定值,并返回旧值。
  • long getAndSet(int i, long newValue)
  • // 以原子方式将索引i的元素加1。
  • long incrementAndGet(int i)
  • // 最终将位置i的元素设置为给定值。
  • void lazySet(int i, long newValue)
  • // 返回该数组的长度。
  • int length()
  • // 将位置i的元素设置为给定值。
  • void set(int i, long newValue)
  • // 返回数组当前值的字符串表示形式。
  • // 以原子方式使用给定的方法获取的值更新索引为i位置上的元素的值,并返回旧值,自JDK1.8起提供,LongUnaryOperator会被多次调用
  • public final long getAndUpdate(int i, LongUnaryOperator updateFunction)
  • // 以原子方式使用给定的方法获取的值更新索引为i位置上的元素的值,并返回新值,自JDK1.8起提供,LongUnaryOperator会被多次调用
  • public final long updateAndGet(int i, LongUnaryOperator updateFunction)
  • // 以原子方式使用给定的方法获取的值将x作为参数更新索引为i位置上的元素的值,并返回旧值,自JDK1.8起提供,LongBinaryOperator会被多次调用
  • public final long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction)
  • // 以原子方式使用给定的方法获取的值将x作为参数更新索引为i位置上的元素的值,并返回新值,自JDK1.8起提供,LongBinaryOperator会被多次调用
  • public final long accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction)
  • String toString()
  • // 如果当前值等于预期值,则以原子方式将该值设置为给定的更新值。
  • boolean weakCompareAndSet(int i, long expect, long update)

1.2. AtomicLongArray源码解析

AtomicLongArray源码源码如下(基于JDK 1.8.0_101):

  • package java.util.concurrent.atomic;
  • import java.util.function.LongUnaryOperator;
  • import java.util.function.LongBinaryOperator;
  • import sun.misc.Unsafe;
  • class AtomicLongArray implements java.io.Serializable {
  • // 支持序列化
  • private static final long serialVersionUID = -2308431214976778248L;
  • // 用于CAS操作的Unsafe类
  • private static final Unsafe unsafe = Unsafe.getUnsafe();
  • /**
  • * 获取数组对象头的长度,后面会根据这个长度来定位数组元素,
  • * 如果要访问第N个元素的话,你的偏移量offset应该是 base + N * arrayScale
  • */
  • private static final int base = unsafe.arrayBaseOffset(long[].class);
  • // 用于标识元素长度的shift, << shift 即为一个元素的长度,本例中shift为3,即每个元素的长度为8位
  • private static final int shift;
  • // 用于装载数据的数组
  • private final long[] array;
  • static {
  • // 获取long型数组中元素的大小(scale为8)
  • int scale = unsafe.arrayIndexScale(long[].class);
  • // 确保scale是2的次方,即保证为1, 2, 4, 8, 16...
  • if ((scale & (scale - 1)) != 0)
  • throw new Error("data type scale not a power of two");
  • /**
  • * Integer.numberOfLeadingZeros(scale); 得到的是当scale填满Integer总位数需要的0的个数
  • * 如在本例中,scale为8,即0b1000,满32位时即为 0b0000 0000 0000 0000 0000 0000 0000 1000
  • * 需要在前面填28个0,所以 Integer.numberOfLeadingZeros(scale) 的值即为28
  • * shift为3,<< shift 即为一个元素的长度,即每个元素的长度为8位
  • */
  • shift = 31 - Integer.numberOfLeadingZeros(scale);
  • }
  • // 检查索引是否越界
  • private long checkedByteOffset(int i) {
  • if (i < 0 || i >= array.length)
  • throw new IndexOutOfBoundsException("index " + i);
  • return byteOffset(i);
  • }
  • // 返回索引i位置的元素相对于对象头的起始位置偏移量
  • private static long byteOffset(int i) {
  • /**
  • * 本例中shift为3,<< shift 相当于乘以8
  • * 因此 ((long) i << shift) 就是用于计算索引i位置的元素相对于对象头尾的起始位置偏移量
  • * ((long) i << shift) + base 则为相对于对象头的起始位置偏移量
  • */
  • return ((long) i << shift) + base;
  • }
  • // 根据传入length构造数组
  • public AtomicLongArray(int length) {
  • array = new long[length];
  • }
  • // 根据传入数组构造数组,使用克隆
  • public AtomicLongArray(long[] array) {
  • // Visibility guaranteed by final field guarantees
  • this.array = array.clone();
  • }
  • // 获取数组长度
  • public final int length() {
  • return array.length;
  • }
  • // 获取索引为i位置上的元素
  • public final long get(int i) {
  • return getRaw(checkedByteOffset(i));
  • }
  • // 获取array数组的offset偏移量上的元素
  • private long getRaw(long offset) {
  • return unsafe.getLongVolatile(array, offset);
  • }
  • // 设置索引为i位置上的元素的值
  • public final void set(int i, long newValue) {
  • unsafe.putLongVolatile(array, checkedByteOffset(i), newValue);
  • }
  • // 延时设置索引为i位置上元素的值
  • public final void lazySet(int i, long newValue) {
  • unsafe.putOrderedLong(array, checkedByteOffset(i), newValue);
  • }
  • // 设置索引为i位置上的元素的值,返回旧值
  • public final long getAndSet(int i, long newValue) {
  • return unsafe.getAndSetLong(array, checkedByteOffset(i), newValue);
  • }
  • // 对索引为i位置上的元素做CAS更新
  • public final boolean compareAndSet(int i, long expect, long update) {
  • return compareAndSetRaw(checkedByteOffset(i), expect, update);
  • }
  • // 对偏移量为offset位置上的元素做CAS更新
  • private boolean compareAndSetRaw(long offset, long expect, long update) {
  • return unsafe.compareAndSwapLong(array, offset, expect, update);
  • }
  • // 实际上就是调用的compareAndSet
  • public final boolean weakCompareAndSet(int i, long expect, long update) {
  • return compareAndSet(i, expect, update);
  • }
  • // 使索引为i位置上的元素的值+1,返回旧值
  • public final long getAndIncrement(int i) {
  • return getAndAdd(i, 1);
  • }
  • // 使索引为i位置上的元素的值-1,返回旧值
  • public final long getAndDecrement(int i) {
  • return getAndAdd(i, -1);
  • }
  • // 使索引为i位置上的元素的值+delta,返回旧值
  • public final long getAndAdd(int i, long delta) {
  • return unsafe.getAndAddLong(array, checkedByteOffset(i), delta);
  • }
  • // 使索引为i位置上的元素的值+1,返回新值
  • public final long incrementAndGet(int i) {
  • return getAndAdd(i, 1) + 1;
  • }
  • // 使索引为i位置上的元素的值-1,返回新值
  • public final long decrementAndGet(int i) {
  • return getAndAdd(i, -1) - 1;
  • }
  • // 使索引为i位置上的元素的值+delta,返回新值
  • public long addAndGet(int i, long delta) {
  • return getAndAdd(i, delta) + delta;
  • }
  • /**
  • * 以原子方式使用给定的方法获取的值更新索引为i位置上的元素的值,并返回旧值,自JDK1.8起提供
  • * LongUnaryOperator会被多次调用
  • */
  • public final long getAndUpdate(int i, LongUnaryOperator updateFunction) {
  • long offset = checkedByteOffset(i);
  • long prev, next;
  • do {
  • prev = getRaw(offset);
  • next = updateFunction.applyAsLong(prev);
  • } while (!compareAndSetRaw(offset, prev, next));
  • return prev;
  • }
  • /**
  • * 以原子方式使用给定的方法获取的值更新索引为i位置上的元素的值,并返回新值,自JDK1.8起提供
  • * LongUnaryOperator会被多次调用
  • */
  • public final long updateAndGet(int i, LongUnaryOperator updateFunction) {
  • long offset = checkedByteOffset(i);
  • long prev, next;
  • do {
  • prev = getRaw(offset);
  • next = updateFunction.applyAsLong(prev);
  • } while (!compareAndSetRaw(offset, prev, next));
  • return next;
  • }
  • /**
  • * 以原子方式使用给定的方法获取的值将x作为参数更新索引为i位置上的元素的值,并返回旧值,自JDK1.8起提供
  • * LongBinaryOperator会被多次调用
  • */
  • public final long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction) {
  • long offset = checkedByteOffset(i);
  • long prev, next;
  • do {
  • prev = getRaw(offset);
  • next = accumulatorFunction.applyAsLong(prev, x);
  • } while (!compareAndSetRaw(offset, prev, next));
  • return prev;
  • }
  • /**
  • * 以原子方式使用给定的方法获取的值将x作为参数更新索引为i位置上的元素的值,并返回新值,自JDK1.8起提供
  • * LongBinaryOperator会被多次调用
  • */
  • public final long accumulateAndGet(int i, long x,
  • LongBinaryOperator accumulatorFunction) {
  • long offset = checkedByteOffset(i);
  • long prev, next;
  • do {
  • prev = getRaw(offset);
  • next = accumulatorFunction.applyAsLong(prev, x);
  • } while (!compareAndSetRaw(offset, prev, next));
  • return next;
  • }
  • public String toString() {
  • int iMax = array.length - 1;
  • if (iMax == -1)
  • return "[]";
  • StringBuilder b = new StringBuilder();
  • b.append('[');
  • for (int i = 0; ; i++) {
  • b.append(getRaw(byteOffset(i)));
  • if (i == iMax)
  • return b.append(']').toString();
  • b.append(',').append(' ');
  • }
  • }
  • }

有了前一篇为AtomicLong源码的分析,对AtomicLongArray的源码分析就要简单得多。可以发现,其实AtomicLongArray的大多数操作依旧是使用Unsafe类的某些方法来进行了,唯一的区别是在AtomicLong中针对的对象是AtomicLong对象,而AtomicLongArray针对的对象是数组对象:

  • public final long getAndAdd(int i, long delta) {
  • return unsafe.getAndAddLong(array, checkedByteOffset(i), delta);
  • }

这里就不对这些普通操作一一作解释了。

我们需要研究一下在数组中如何定位元素。从整体的源码可以得知,对数组元素的各类操作还是使用Unsafe进行的,而Unsafe底层对对象的获取是直接访问地址。我们观察下面的源码片段:

  • /**
  • * 获取数组对象头的长度,后面会根据这个长度来定位数组元素,
  • * 如果要访问第N个元素的话,你的偏移量offset应该是 base + N * arrayScale
  • */
  • private static final int base = unsafe.arrayBaseOffset(long[].class);
  • // 用于标识元素长度的shift, << shift 即为一个元素的长度,本例中shift为3,即每个元素的长度为8位
  • private static final int shift;
  • // 用于装载数据的数组
  • private final long[] array;
  • static {
  • // 获取long型数组中元素的大小(scale为8)
  • int scale = unsafe.arrayIndexScale(long[].class);
  • // 确保scale是2的次方,即保证为1, 2, 4, 8, 16...
  • if ((scale & (scale - 1)) != 0)
  • throw new Error("data type scale not a power of two");
  • /**
  • * Integer.numberOfLeadingZeros(scale); 得到的是当scale填满Integer总位数需要的0的个数
  • * 如在本例中,scale为8,即0b1000,满32位时即为 0b0000 0000 0000 0000 0000 0000 0000 1000
  • * 需要在前面填28个0,所以 Integer.numberOfLeadingZeros(scale) 的值即为28
  • * shift为3,<< shift 即为一个元素的长度,即每个元素的长度为8位
  • */
  • shift = 31 - Integer.numberOfLeadingZeros(scale);
  • }
  • // 返回索引i位置的元素相对于对象头的起始位置偏移量
  • private static long byteOffset(int i) {
  • /**
  • * 本例中shift为3,<< shift 相当于乘以8
  • * 因此 ((long) i << shift) 就是用于计算索引i位置的元素相对于对象头尾的起始位置偏移量
  • * ((long) i << shift) + base 则为相对于对象头的起始位置偏移量
  • */
  • return ((long) i << shift) + base;
  • }

在上面的代码中base变量就是long型数组的对象头长度,通过unsafe.arrayBaseOffset(long[].class)获取的。而在静态代码块中,通过unsafe.arrayIndexScale(long[].class)获取了数组中每个元素的长度(存放的是引用),这里使用了一种巧妙的方式来提高后期访问元素时的效率:将每个元素的长度表示转换为1的左移操作。

在静态代码块中限定了元素的长度值为2的次方,使用下面的代码限定的:

  • if ((scale & (scale - 1)) != 0)
  • throw new Error("data type scale not a power of two");

然后通过shift = 31 - Integer.numberOfLeadingZeros(scale)计算出了使用1左移的方式将长度值转换为二进制所需要移动的位数。最后计算出的shift为3,即得到十进制8时需要将二进制1左移3位即可得到,而左移3位其实就是乘以8操作。

得到了shift之后,在之后的byteOffset(int i)方法中就得到了应用,为了得到索引i上的元素相对于对象头的偏移量,先计算索引i相对于对象头尾部的偏移量,计算方式是乘以每个元素的长度(即8),这里使用了((long) i << shift)的方式进行快速计算,然后加上对象头的长度,就可以得到索引i相对于对象头尾部的偏移量。

2. AtomicLongArray示例

下面是对AtomicLongArray类型的常用操作的示例:

  • public class AtomicLongArrayTest {
  • public static void main(String[] args) {
  • // 新建AtomicLongArray对象
  • long[] arrLong = new long[]{10, 20, 30, 40, 50};
  • AtomicLongArray atomicLongArray = new AtomicLongArray(arrLong);
  • atomicLongArray.set(0, 100);
  • for (int i = 0, len = atomicLongArray.length(); i < len; i++)
  • System.out.printf("get(%d) : %s\n", i, atomicLongArray.get(i));
  • System.out.printf("%20s : %s\n", "getAndDecrement(0)", atomicLongArray.getAndDecrement(0));
  • System.out.printf("%20s : %s\n", "decrementAndGet(1)", atomicLongArray.decrementAndGet(1));
  • System.out.printf("%20s : %s\n", "getAndIncrement(2)", atomicLongArray.getAndIncrement(2));
  • System.out.printf("%20s : %s\n", "incrementAndGet(3)", atomicLongArray.incrementAndGet(3));
  • System.out.printf("%20s : %s\n", "addAndGet(100)", atomicLongArray.addAndGet(0, 100));
  • System.out.printf("%20s : %s\n", "getAndAdd(100)", atomicLongArray.getAndAdd(1, 100));
  • System.out.printf("%20s : %s\n", "compareAndSet()", atomicLongArray.compareAndSet(2, 31, 1000));
  • System.out.printf("%20s : %s\n", "get(2)", atomicLongArray.get(2));
  • }
  • }

运行结果如下:

  • get(0) : 100
  • get(1) : 20
  • get(2) : 30
  • get(3) : 40
  • get(4) : 50
  • getAndDecrement(0) : 100
  • decrementAndGet(1) : 19
  • getAndIncrement(2) : 30
  • incrementAndGet(3) : 41
  • addAndGet(100) : 199
  • getAndAdd(100) : 19
  • compareAndSet() : true
  • get(2) : 1000