zoukankan      html  css  js  c++  java
  • 原子数组AtomicIntegerArray实现原理简析

    1 前言

    JDK官方提供了3个原子数组,它们提供了原子更新数组中元素的能力,它们主要借助Unsafe类实现其核心功能。

    • AtomicIntegerArray:原子更新整型数组里的元素

    • AtomicLongArray:原子更新长整型数组里的元素。

    • AtomicReferenceArray:原子更新引用类型数组里的元素。

    在分析原子数组之前,我们先来了解Java对象的内存布局,Java对象由对象头和实例数据两部分组成。

    下图中MarkWord包含对象的hashCode、锁信息、垃圾回收的分代信息等,占32/64位;Class Metadata Pointer表示一个此对象数据类型的Class对象(虚拟机中的Klass对象)的指针,占32/64位;ArrayLength是数组对象特有的内容,表示数组的长度,占32位。数组对象的实例数据是各个元素的值或引用,普通对象的实例数据是各实例字段的值或引用。另外为了快速内存分配、快速内存寻址、提高性能,Java语言规范要求Java对象要做内存对齐处理,每个对象占用的内存字节数必须是8的倍数,若不是则要填零补足对齐。

    从下图可以看出,字段与对象头之间的偏移量是固定的,只要知道字段的相对偏移量和对象起始地址,我们就能获取此字段的绝对内存地址(fieldAddress=objAddress+fieldOffset),根据此绝对内存地址,我们就能忽略访问修饰符的限制而可直接读取/修改此字段的值或引用。

    数组对象的元素内存定址,相对对于普通对象的字段定址有些不一样,它要先计算出对象头的长度,作为基础偏移量;由于数组元素的数据类型是相同的,每个元素的值或引用所占内存空间是相同的,因此将元素值或引用或占内存作为每两相邻元素的相对偏移量。根据对象起始位置、基础偏移量、相邻元素相对偏移量及数组下标,就可以获取到某个元素值或引用的绝对内存地址(itemAddress=arrayAddress+baseOffset+index*indexOffset),进而通过绝对内存地址读取或修改此元素的值或引用。

    2 源码分析

    这里以JDK1.8的AtomicIntegerArray为例,对原子数组做出简单的实现原理分析。

    (1) 主要字段

    //Unsafe是所有并发工具的基础工具类
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //数组对象头到数组首元素间的地址偏移量
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    //数组中相邻元素的地址偏移量的位移表示形式(若shift=x,那么相邻元素的偏移量就是"1<<shift")
    private static final int shift;
    //实际存放元素的数组
    private final int[] array;

    静态块主要是对静态常量shift初始化。

    static {
        int scale = unsafe.arrayIndexScale(int[].class);//表示相邻元素的地址偏移量
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");//非法的,scale一定是2的幂次方
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    scale表示相邻元素的地址偏移量,因为内存对齐的原因,scale一定是2n。而shift是相邻元素的地址偏移量的位移表示形式,Integer.numberOfLeadingZeros(scale)取scale进制前导零的个数,所以shift是scale二进制有效位数减1,即有效的0的个数。
    如scale=16,即scale=0b00000000_00000000_00000000_00010000 , 那么shift=4。


    (2) 方法

    构造方法

    AtomicIntegerArray(int)创建指定长度的原子数组,AtomicIntegerArray(int)利用克隆(这里是深度克隆,源数组和原子数组互不影响)将指定的普通数组包装成原子数组.

    public AtomicIntegerArray(int length) {
        array = new int[length];
    }
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }

    获取指定下标元素get(int)

    get()方法主要利用byteOffset根据公式offset=base+i*scale求出数组对象的起始位置下标元素的偏移量offset,然后利用unsafe.getIntVolatile根据公式indexAddr=arrayAddr+offset求出出指定偏移位置的元素值

        public final int get(int i) {
            return getRaw(checkedByteOffset(i));
        }
        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; //i*2^shift +base 即,"i*元素间距+对象头长度"
        }
    
        private int getRaw(long offset) {
            return unsafe.getIntVolatile(array, offset);//利用unsafe方法求出指定偏移位置的int值
        }

    更新指定下标元素set(int,int)

    set()方法与get方法类似,都要先调用checkedByteOffset计算偏移量offset,不过此set方法要调用putIntVolatile在指定内存位置修改值而已。

    public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }

    getAndSet(int,int)

    先获取值再设置新值,同样先调用checkedByteOffset计算偏移量offset,然后getAndSetInt进行CAS自旋更新指定的元素。

    public final int getAndSet(int i, int newValue) {
        return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
    }
    public final int getAndSetInt(Object o, long offset, int newValue) {//unsafe的方法
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
    }

    compareAndSet()CAS更新期望的元素,先调用checkedByteOffset计算偏移量offset,然后调用compareAndSwapInt尝试CAS更新元素。

    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);
    }

    3 使用示例

    class AtomicIntArrayDemo {
    
         public static void main(String[] args) {
             AtomicIntegerArray array = new AtomicIntegerArray(new int[]{45,23,13,47,12,42});
             for (int i = 0; i <array.length() ; i++) {
                 final int j=i;
                 new Thread(()->{
                     array.compareAndSet(j, 13, 31);
                     array.getAndAdd(j, 2);
                     array.decrementAndGet(j);
                     array.getAndSet(j, array.get(j) - 1);
                 }).start();
    
             }
             System.out.println(array);
         }
    }



  • 相关阅读:
    centos 用户管理
    rsync 实验
    文件共享和传输
    PAT 1109 Group Photo
    PAT 1108 Finding Average
    PAT 1107 Social Clusters
    PAT 1106 Lowest Price in Supply Chain
    PAT 1105 Spiral Matrix
    PAT 1104 Sum of Number Segments
    PAT 1103 Integer Factorization
  • 原文地址:https://www.cnblogs.com/gocode/p/analysis-source-code-AtomicIntegerArray.html
Copyright © 2011-2022 走看看