Java併發學習(八)-AtomicIntegerArray陣列型別類
前一篇文章學習了AtomicXXX基本資料型別類,可以為int,boolean或者reference型別,也就是單個元素的原子類。那麼陣列型別呢?
下面以AtomicIntegerArray
為例進行分析。
AtomicXXXArray包括三種具體類:AtomicIntegerArray
,AtomicLongArray
,AtomicReferenceArray
。
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[]型別。