1. 程式人生 > 其它 >AtomicIntegerArray 原始碼解析(基於 JDK 1.8)

AtomicIntegerArray 原始碼解析(基於 JDK 1.8)

文章目錄


AtomicIntegerArray 可以原子的更新 int[] 中某個物件。

在找到陣列第0個物件的偏移量之後,由於陣列中每個物件是順序排放的,可以根據物件大小計算出陣列中某索引的偏移量,然後通過 Unsafe 相關的方法來獲取或者修改。

1 偏移量的計算

假設陣列中第 0 個物件的在陣列中的偏移量為 x,每個物件的大小為 y,那麼陣列中第 i 個元素在陣列中的偏移量為 x+i*y。下面是具體實現。

base 表示的就是第 0 個物件的偏移量 x,scale 或者 1<< shift 或者$2^{shift} 都表示每個物件的大小 y。

有幾個點需要理解一下

  1. 如果 scale 是 2 的冪,則一定有 scale & (scale - 1)) == 0,這個是充要條件。

    證明:a 表示 scale是2的冪,b表示 scale & (scale - 1)) == 0。

    a -> b:

    假設scale是   ...00100...
    則scale-1是   ...00011...
    兩個 & 得到   ...00000...
    

    b -> a:

    用逆否命題,非a -> 非b:
    如果scale不是2的冪,在大於0的情況下,
    可以找到最高位的1,由於不是2的冪,
    則最高位後面至少有一位是1
    即,   ...00100...1...
    -1 為  ...00100...0...
    兩個 & 得到 非零
    
  2. $2^{shift} 就是 scale。

    說明:scale 此時為 2 的冪,只有一個1,從高到低為 a個0,1個1, b個0。

    其中 a+1+b =32。

    由於 numberOfLeadingZeros 返回 a,可知 shift 是 b,此時 1<<shift 正好表示 scale。

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private
static final Unsafe unsafe = Unsafe.getUnsafe(); //base 表示初始位置 x private static final int base = unsafe.arrayBaseOffset(int[].class); //2^shift 等於 scale,表示每個物件的大小 private static final int shift; private final int[] array; static { int scale = unsafe.arrayIndexScale(int[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); 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) { //return i*scale + base; return ((long) i << shift) + base; } ... }

2 其他

首先介紹初始化,初始化有兩種,一種是給定長度,生成新陣列;另一種是給定陣列,將陣列設定為陣列的深拷貝,防止不經過這個類修改而是直接修改外界陣列,導致結果錯誤。

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

其他的方法都使用 Unsafe,前面已經算出位置了,那就直接修改相應位置即可。有需要可以去看我的 Unsafe 講解。

public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }
    
private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

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

public final int getAndUpdate(int i, IntUnaryOperator updateFunction) {
        long offset = checkedByteOffset(i);
        int prev, next;
        do {
            prev = getRaw(offset);
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSetRaw(offset, prev, next));
        return prev;
    }

public final int getAndSet(int i, int newValue) {
        return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
    }
public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }
//其他方法省略

3 使用

import java.util.concurrent.atomic.AtomicIntegerArray;

public class A{

    public static void main(String[] args) throws Exception {

            AtomicIntegerArray array = new AtomicIntegerArray(new int[]{10,20,30,40,50});
            for (int i = 0; i <array.length() ; i++) {
                int j=i;
                new Thread(()->{
                    array.compareAndSet(j, 10, 30);
                    array.decrementAndGet(j);
                    array.getAndSet(j, array.get(j) - 1);
                }).start();

            }

            Thread.sleep(2000);
            System.out.println(array);

    }
}

下面是我的公眾號,Java與大資料進階,分享 Java 與大資料筆面試乾貨,歡迎關注
在這裡插入圖片描述