AtomicIntegerArray 原始碼解析(基於 JDK 1.8)
阿新 • • 發佈:2021-03-29
文章目錄
AtomicIntegerArray 可以原子的更新 int[] 中某個物件。
在找到陣列第0個物件的偏移量之後,由於陣列中每個物件是順序排放的,可以根據物件大小計算出陣列中某索引的偏移量,然後通過 Unsafe 相關的方法來獲取或者修改。
1 偏移量的計算
假設陣列中第 0 個物件的在陣列中的偏移量為 x,每個物件的大小為 y,那麼陣列中第 i 個元素在陣列中的偏移量為 x+i*y。下面是具體實現。
base 表示的就是第 0 個物件的偏移量 x,scale 或者 1<< shift 或者$2^{shift} 都表示每個物件的大小 y。
有幾個點需要理解一下
-
如果 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^{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 與大資料筆面試乾貨,歡迎關注