Java多线程之JUC原子类 - 以原子方式操做数组AtomicLongArray

1、AtomicLongArray的介绍

    AtomicLongArray是JUC提供的以原子方式操做数组的一个类,存储在AtomicLongArray中的数组元素可以以原子方式进行更新,它原子变量的实现依赖于sun.misc的Unsafe类提供的CAS操做和volatile的多线程内存可见性语义,下面咱们看下该类的数据结构。java

2、AtomicLongArray数据结构

public class AtomicLongArray implements java.io.Serializable {
    private static final long serialVersionUID = -2308431214976778248L;
    // unsafe变量是AutomicLongArray实现数组原子化更新的核心,数组元素的修改操做由unsafe的CAS相关操做完成
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // base是数组首个元素偏移地址
    private static final int base = unsafe.arrayBaseOffset(long[].class);
    // 数组元素的偏移量
    private static final int shift;
    // 保存元素的数组
    private final long[] array;

    static {
        // 获取数组元素的增量偏移
        int scale = unsafe.arrayIndexScale(long[].class);
        // 判断是否是2的倍数
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        // 获取long型元素的偏移量
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

}

 

3、AtomicLongArray源码解析

1 - long checkAndByteOffSet(int i) 方法

    checkAndByteOffset(int i)方法主要用于判断索引值是否越界,若越界则抛出越界异常,不然计算索引值对应的元素在数组中的内存偏移量数组

private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    方法首先校验了索引的范围,若索引i越界则抛出IndexOutOfBoundsException异常,不然调用byteOffSet方法,咱们进入byteOffset方法源码内部看下方法内部作了什么数据结构

private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

    方法很简单,根据索引下标i计算出每一个元素的内存地址,元素内存地址=首地址偏移+每一个元素的相对于数组偏移,每一个数组元素相对数组的偏移量等于元素所占内存8*数组下标i。多线程

 

2 -  long get(int i)方法 - 以原子方式获取数组指定索引位置元素

public final long get(int i) {
        return getRaw(checkedByteOffset(i));
    }

        方法逻辑不难,首先调用checkedByteOffset方法校验数组的索引下标i,校验经过返回对应下标元素的内存地址,接着将该地址做为参数调用getRow方法,进入该方法源码:spa

private long getRaw(long offset) {
        return unsafe.getLongVolatile(array, offset);
    }

    该方法在内部调用了unsafe.getLongVolitile方法获取数组指定下标i的元素。线程

    long get(int i)方法的基本逻辑总结以下:1)校验数组下标i,若越界抛出异常,不然获取索引下标元素的内存地址;2)基于元素的内存地址获取指定下标的数组元素。code

 

3 - void set(int i, long value) - 以原子方式设置数组指定下标位置值

/**
     * 以原子方式设置数组指定下标位置值为newValue
     */
    public final void set(int i, long newValue) {
        unsafe.putLongVolatile(array, checkedByteOffset(i), newValue);
    }

        set方法的基本逻辑与get相似,先校验数组下标i,若校验经过返回数组下标位置的地址,基于该地址和内部数组引用array设置新值newValue.索引

 

4 - long incrementAndGet方法 - 以原子方式在指定下标i元素加1,返回更新后的值

/**
     * 以原子方式在指定下标i元素加1,返回更新后的值
     */
    public final long incrementAndGet(int i) {
        return getAndAdd(i, 1) + 1;
    }

    看下getAndAdd方法源码内存

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

    到这里就清楚了,该方法首先对数组索引下标i作了范围校验,校验经过获取数组下标位置的地址,基于该地址调用unsafe的getAndAddLong对该位置元素使用CAS操做更新,注意此时返回的long值时更新前的因此咱们在调用处incrementAndGet方法还要加1。rem

5 - void lazySet方法 - 延时更新数组下标元素,优先保证数据的修改操做,而下降对可见性的要求

/**
     * 更新数组指定下标i处的值为newValue,与set的区别在于它是延时而不是当即更新,适用于对实时性要求不高的场景
     */
    public final void lazySet(int i, long newValue) {
        unsafe.putOrderedLong(array, checkedByteOffset(i), newValue);
    }

6 - 其余方法

    其余方法逻辑相似就不一一分析了

相关文章
相关标签/搜索