JUC针对数组元素的原子封装,先看AtomicIntegerArray。
private static final Unsafe unsafe = Unsafe.getUnsafe(); //arrayBaseOffset获取数组首个元素地址偏移 private static final int base = unsafe.arrayBaseOffset(int[].class); //shift就是数组元素的偏移量 private static final int shift; private final int[] array; static { //scale数组元素的增量偏移 int scale = unsafe.arrayIndexScale(int[].class); //对于int型数组,scale是4,用二进制&操作判断是否是2的倍数,这个判断nb if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); //这里是处理int型的偏移量,shift是2 shift = 31 - Integer.numberOfLeadingZeros(scale); } /** 计算数组中元素的地址 */ 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; }
public class UnsafeTest { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); int[] a = new int[]{1,2,5}; //int[]首个元素的偏移 int arrayBaseOffset = unsafe.arrayBaseOffset(a.getClass()); //数组中元素的增量偏移 int arrayIndexScale = unsafe.arrayIndexScale(a.getClass()); System.out.println(arrayBaseOffset); System.out.println(arrayIndexScale); //通过首地址的偏移+增量偏移,获取数组元素值 System.out.println(unsafe.getIntVolatile(a, arrayBaseOffset+arrayIndexScale)); } }
假设数组首地址14,int型,每个4个字节,所以取第0个元素地址就是,14+(0*4),第2个元素14+(1*4),第i个地址为14+(i*4),采用移位的话就是(4的2进制是2)14+(i<<2),对于AtomicLongArray取数组元素就是首地址+(i<<3)。对于AtomicInteger取元素地址就是base+(index<<scale)。
看下AtomicIntegerArray的构造函数
public AtomicIntegerArray(int[] array) { // Visibility guaranteed by final field guarantees this.array = array.clone(); }这里有个说明,采用final来保证数组的可见性,看到这里的时候,郁闷,以前final的使用,重来没想过什么可见性问题,直接用就是了,只好百度,http://www.infoq.com/cn/articles/java-memory-model-6/这里对于final的可见性有详细说明,也是通过加内存屏障来实现。最重要一句: JSR-133专家组增强了final的语义。通过为final域增加写和读重排序规则,可以为java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用),就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。
看下AtomicIntegerArray的一些方法:
public final int get(int i) { return getRaw(checkedByteOffset(i)); } /** 通过unsafe.getIntVolatile这个保证volatile语义, 感觉意思应该是可以当场volatile变量使用 */ private int getRaw(long offset) { return unsafe.getIntVolatile(array, offset); } /** unsafe.putIntVolatile保证volatile语义 */ public final void set(int i, int newValue) { unsafe.putIntVolatile(array, checkedByteOffset(i), newValue); } /** unsafe.putOrderedInt这个前面一文提过, 去掉了storeLoad内存屏障,只有storestore屏障,只保证修改成功,不保证修改后其他处理器立即就可以看到 */ public final void lazySet(int i, int newValue) { unsafe.putOrderedInt(array, checkedByteOffset(i), newValue); } /** 这里是通过while循环来实现最终设置成功 */ public final int getAndSet(int i, int newValue) { long offset = checkedByteOffset(i); while (true) { int current = getRaw(offset); if (compareAndSetRaw(offset, current, newValue)) return current; } } /** 对于数组的cas,只是多了个先取地址的操作,其他还是底层的unsafe */ 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); }
AtomicLongArray跟AtomicIntegerArray类同。
AtomicReferenceArray多了个arrayFieldOffset,用来在从stream读数组设置到array内存偏移地址。
参考:
http://www.infoq.com/cn/articles/java-memory-model-6/ final关键字的可见性
http://brokendreams.iteye.com/blog/2250117