1. 程式人生 > >Java併發學習(八)-AtomicIntegerArray陣列型別類

Java併發學習(八)-AtomicIntegerArray陣列型別類

前一篇文章學習了AtomicXXX基本資料型別類,可以為int,boolean或者reference型別,也就是單個元素的原子類。那麼陣列型別呢?
下面以AtomicIntegerArray為例進行分析。
AtomicXXXArray包括三種具體類:AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

What is AtomicIntegerArray

具體的介紹,都已經在開頭講過了,AtomicIntegerArray有以下特點:

  • 可以存放int數值的原子性陣列
  • 以整個陣列物件為單位,裡面的元素操作都是原子性的

實現

從以前的文章分析可以瞭解,一般原子類的實現都是volatile+CAS的方式,那麼AtomicIntegerArray也是麼?
首先,作為原子性陣列,裡面肯定有個int型別陣列,那陣列是volatile型別麼?
前面文章分析可以知道,如果把陣列定義為volatile型別,那麼其裡面陣列元素在讀寫方面是沒有volatile語義。 可以參看:Java併發學習(二)-JMM

直接看程式碼中定義:

private final int[] array;

定義了一個final的int型別陣列,final的記憶體語義則是使用時,一定是已經直接初始化或者通過構造方法初始化好的。

//獲取int[]在記憶體中的初始地址。
private static final int base = unsafe.arrayBaseOffset(int[].class); //用來儲存移位個數 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"
); //得出scale為2的幾次方,即需要移位個數 shift = 31 - Integer.numberOfLeadingZeros(scale); } //檢查第i個元素的地址值。 private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } //當前索引i*shift(偏移位置) + base(基礎位置) private static long byteOffset(int i) { return ((long) i << shift) + base; } //獲取第i個元素的值 public final int get(int i) { return getRaw(checkedByteOffset(i)); } //通過地址值來獲取偏移量的元素值。 private int getRaw(long offset) { return unsafe.getIntVolatile(array, offset); } //用cas方式,在元素i的位置設定新值 public final void set(int i, int newValue) { unsafe.putIntVolatile(array, checkedByteOffset(i), newValue); }

核心程式碼可以看上面,具體都有註釋,可能有點模糊,這裡再說說核心思想:
我們知道,陣列在記憶體中是連續儲存的,如下:
這裡寫圖片描述

並且數組裡面各個元素類別都是相同的,所以佔有的空間也都是一樣大的,假設上面陣列為int型別的array,並且array的地址為n,所以可以計算出array[1]為array+4,array[2]為
array+4*2,array[3]為array+4*3 。
所以這樣在AtomicIntegerArray裡面,我們可以通過base,i,scale和shift,能夠計算出陣列中任意元素的位置以及獲取值,這樣一來,對陣列的操作就可以轉化為對單個元素的操作。
開始被一個問題困擾了一會兒,array陣列是final型別,保證了:

  • array在使用的時候,已經初始化了
  • array不能再重新指向其他物件

但是,array數組裡面並不是volatile型別的,能確保可見性麼?

我們再來看看它的get方法和set方法:
get方法:

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

set方法:

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

    //lazySet,即普通set,效能高
    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }

    //原子性的獲取並且set
    public int getAndSet(int i, int newValue) {
        return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
    }

如上我們可以看到,呼叫的都是unsafe裡面具有volatile語義的方法,也就是整個通過記憶體xia地址對陣列元素的操作,也是有volatile語義的,即具有可見性。

AtomicLongArray和AtomicObjectArray

這兩者與AtomicIntegerArray實現基本一致,只是陣列物件分別為long[]和Object[]型別。