同步数据结构之原子数组类

引言

通过原子类序章我们知道Java并发包提供的原子类共分5类,这里开始介绍第二类数组类,其实也就是通过数组下标原子更新基本类型和引用类型的数组中的元素,它们是:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray。由于AtomicLongArray与AtomicIntegerArray非常类似,所以我还是重点对AtomicIntegerArray和AtomicReferenceArray进行分析。

 

AtomicIntegerArray

在分析其具体原子更新方法之前,我们有必要先了解一下AtomicIntegerArray的构造方法和一些确定数组下标的基础方法,这将有利于我们对后面其它所有方法的理解。

 

首先构造方法

 1 private final int[] array;  //内部维护了一个final修饰的整形数组  
 2   
 3 public AtomicIntegerArray(int length) {  
 4     array = new int[length]; //构造一个指定长度的所有元素都是0的整形原子数组  
 5 }  
 6   
 7 public AtomicIntegerArray(int[] array) {  
 8     // Visibility guaranteed by final field guarantees  
 9     this.array = array.clone();//构造一个和指定数组长度、内容一样的数组  
10 }  

它有两个构造方法, 注意第二个有实际内容的构造方法这里并没有使用volatile写来保证可见性,但是由于成员属性array是final关键字修饰的,根据Java内存模型JMM之五final内存语义对final域的JMM规则,只要没有在构造方法中逸出this指针,对final域的写入也能够在构造完成将this引用赋值给其它变量时变得立即可见。

基础方法

 1 private static final Unsafe unsafe = Unsafe.getUnsafe();  
 2 private static final int base = unsafe.arrayBaseOffset(int[].class);//第一个元素的偏移地址  
 3 private static final int shift;  
 4   
 5 static {  
 6     int scale = unsafe.arrayIndexScale(int[].class);//数组中单个元素占用的内存空间  
 7     if ((scale & (scale - 1)) != 0) //确保是2的幂,即2的多少次方  
 8         throw new Error("data type scale not a power of two");  
 9     shift = 31 - Integer.numberOfLeadingZeros(scale);  
10 }  
11   
12 //更加下标i返回对应的数组元素的偏移地址  
13 private long checkedByteOffset(int i) {  
14     if (i < 0 || i >= array.length)  
15         throw new IndexOutOfBoundsException("index " + i);  
16   
17     return byteOffset(i);  
18 }  
19   
20 private static long byteOffset(int i) {  
21     return ((long) i << shift) + base;//通过移位操作计算指定下标的数组元素偏移量  
22 }  
23 //通过下标获取数组元素,最终是将下标计算出偏移量  
24 public final int get(int i) {  
25     return getRaw(checkedByteOffset(i));  
26 }  
27 //通过偏移量获取数组元素,使用了有volatile特性的get方法,可以立即获取最新的值  
28 private int getRaw(long offset) {  
29     return unsafe.getIntVolatile(array, offset);  
30 }  

以上的基础方法分两部分,第一部分准备数组元素的基础偏移量和间隔偏移,按理说通过unsafe的arrayBaseOffset和arrayIndexScale两个方法就能确定需要的数据,但是事实上它的逻辑却更复杂,首先它要求scale必须是2的多少次方,对于int类型的数组其值为4,long类型的数组其值为8,然后它又通过 31 - Integer.numberOfLeadingZeros(scale) 方法计算出了一个新用于确定数组元素偏移地址的常量shift,关于numberOfLeadingZeros方法,可以很容易了解到它是利用二分查找方返回指定int值的二进制补码表示形式中在最高位的1左边的0的位数,比如:4的二进制补码为100,那么它的最高位1的左边的0的位数就是29;又如8的二进制补码为1000,那么其值就是28,然后再用31减该值,其实得到的结果就是二进制补码形式中最高位1右边的所有位数,为4的时候,shift就是2,为8的时候,shift就是4。

 

第二部分就是利用第一部分得到的常量计算出指定数组下标的元素的偏移量,它最终是通过位移操作完成的,例如,下标0的偏移就是base,int类型时,下标1的偏移1<<2 +4 等于8,下标2的偏移是2<<2 +4=12; long类型时,下标1的偏移1<<3 +8=16,下标2的偏移2<<3 +8=24,所以实际上和直接使用公式scale*i + base计算的结果是一样的,只是这里AtomicIntegerArray使用移位操作来计算结果罢了。

 

接下来,就是AtomicIntegerArray原子更新操作相关的方法了,其实它也主要由AtomicInteger所划分的那四类方法构成,不同的是AtomicIntegerArray更新的是数组中的特定元素而已。

 

1. 简单自更新 

 1 //简单自更新方法1  
 2 public final int getAndIncrement(int i) {  
 3     return getAndAdd(i, 1);  
 4 }  
 5 //简单自更新方法2  
 6 public final int getAndDecrement(int i) {  
 7     return getAndAdd(i, -1);  
 8 }  
 9 //简单自更新方法3  
10 public final int incrementAndGet(int i) {  
11     return getAndAdd(i, 1) + 1;  
12 }  
13 //简单自更新方法4  
14 public final int decrementAndGet(int i) {  
15     return getAndAdd(i, -1) - 1;  
16 }  
17 //最终实现方法,都是利用了unsafe  
18 public final int getAndAdd(int i, int delta) {  
19     return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);  
20 }  
21 //unsafe的getAndAddInt实现  
22 public final int getAndAddInt(Object o, long offset, int delta) {  
23     int v;  
24     do {  
25         v = getIntVolatile(o, offset);  
26     } while (!compareAndSwapInt(o, offset, v, v + delta));  
27     return v;  
28 }  

和 AtomicInteger的简单自更新几乎一样,不同的时此处需要传入更新的下标,然后对指定下标的数组元素进行加减1,它们的实现都是利用了unsafe的getAndAddInt方法:首先使用getIntVolatile获取最新的值,然后通过CAS原子地更新。

2. 简单外更新

 1 //原子的将指定下标的元素更新为新的值  
 2 public final void set(int i, int newValue) {  
 3     unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);  
 4 }  
 5 //延迟可见设置指定下标的元素为新的值  
 6 public final void lazySet(int i, int newValue) {  
 7     unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);  
 8 }  
 9 //原子的设置指定下标的元素为新值,返回旧值  
10 public final int getAndSet(int i, int newValue) {  
11     return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);  
12 }  
13 //CAS操作,旧值没变化的时候才更新  
14 public final boolean compareAndSet(int i, int expect, int update) {  
15     return compareAndSetRaw(checkedByteOffset(i), expect, update);  
16 }  
17   
18 private boolean compareAndSetRaw(long offset, int expect, int update) {  
19     return unsafe.compareAndSwapInt(array, offset, expect, update);  
20 }  
21   
22 public final boolean weakCompareAndSet(int i, int expect, int update) {  
23     return compareAndSet(i, expect, update);  
24 }  
25 //原子的将指定下标的数组元素增加delta,返回旧值。  
26 public final int getAndAdd(int i, int delta) {  
27     return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);  
28 }  
29 //原子的将指定下标的数组元素增加delta,返回新值。  
30 public final int addAndGet(int i, int delta) {  
31     return getAndAdd(i, delta) + delta;  
32 }  
33 //Unsafe的getAndSetInt实现  
34 public final int getAndSetInt(Object o, long offset, int newValue) {  
35     int v;  
36     do {  
37         v = getIntVolatile(o, offset);  
38     } while (!compareAndSwapInt(o, offset, v, newValue));  
39     return v;  
40 }  

它们的实现同样也是利用了CAS+Volatile关键字,不在熬述。

3. 函数式自更新 

 1 public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {  
 2     long offset = checkedByteOffset(i);  
 3     int prev, next;  
 4     do {  
 5         prev = getRaw(offset);  
 6         next = updateFunction.applyAsInt(prev);  
 7     } while (!compareAndSetRaw(offset, prev, next));  
 8     return prev;  
 9 }  
10 //返回新值  
11 public final int updateAndGet(int i, IntUnaryOperator updateFunction) {  
12     long offset = checkedByteOffset(i);  
13     int prev, next;  
14     do {  
15         prev = getRaw(offset);  
16         next = updateFunction.applyAsInt(prev);  
17     } while (!compareAndSetRaw(offset, prev, next));  
18     return next;  
19 }  

同AtomicInteger一样,AtomicIntegerArray也提供了函数式更新指定下标元素的方法,使用的IntUnaryOperator参数类型也是一样的,就不再熬述。

4. 函数式外更新 

 1 public final int getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction) {  
 2     long offset = checkedByteOffset(i);  
 3     int prev, next;  
 4     do {  
 5         prev = getRaw(offset);  
 6         next = accumulatorFunction.applyAsInt(prev, x);  
 7     } while (!compareAndSetRaw(offset, prev, next));  
 8     return prev;  
 9 }  
10 //返回新值  
11 public final int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction) {  
12     long offset = checkedByteOffset(i);  
13     int prev, next;  
14     do {  
15         prev = getRaw(offset);  
16         next = accumulatorFunction.applyAsInt(prev, x);  
17     } while (!compareAndSetRaw(offset, prev, next));  
18     return next;  
19 }  

 同AtomicInteger一样,AtomicIntegerArray也提供了函数式更新指定下标元素的方法,使用的IntBinaryOperator参数类型也是一样的,就不再熬述。

  

同 AtomicIntegerArray相比AtomicLongArray不同的是内部维护的是一个long型的数组变量,其他都是一样的方法。

 

AtomicReferenceArray

说起AtomicReferenceArray,它提供的方法其实和AtomicIntegerArray也几乎类似,不同的是它在内部维护的是一个Object类型的数组,构造方法和通过下标定位数组元素的偏移地址的方式一摸一样。它也和AtomicReference一样分为那四类方法,同样没有AtomicIntegerArray和AtomicLongArray那样可以进行加减的操作方法。

 

另外,AtomicReferenceArray还有一个arrayFieldOffset变量:

 1 private static final long arrayFieldOffset;  
 2 private final Object[] array;  
 3 static {  
 4     try {  
 5         unsafe = Unsafe.getUnsafe();  
 6         arrayFieldOffset = unsafe.objectFieldOffset  
 7             (AtomicReferenceArray.class.getDeclaredField("array"));  
 8     } catch (Exception e) {  
 9         throw new Error(e);  
10     }  
11 }  

 它表示内置对象数组的其实偏移地址,它只有一个用处那就是反序列化,就是如下这个方法:

 1 /** 
 2  * Reconstitutes the instance from a stream (that is, deserializes it). 
 3  */  
 4 private void readObject(java.io.ObjectInputStream s)  
 5     throws java.io.IOException, ClassNotFoundException,  
 6     java.io.InvalidObjectException {  
 7     // Note: This must be changed if any additional fields are defined  
 8     Object a = s.readFields().get("array", null);  
 9     if (a == null || !a.getClass().isArray())  
10         throw new java.io.InvalidObjectException("Not array type");  
11     if (a.getClass() != Object[].class)  
12         a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class);  
13     unsafe.putObjectVolatile(this, arrayFieldOffset, a);  
14 } 

 

posted @ 2021-05-11 16:52  莫待樱开春来踏雪觅芳踪  阅读(156)  评论(0)    收藏  举报