本文首發於一世流雲的專欄: https://segmentfault.com/blog...
Atomic數組,顧名思義,就是能以原子的方式,操做數組中的元素。java
JDK提供了三種類型的原子數組:AtomicIntegerArray
、AtomicLongArray
、AtomicReferenceArray
。segmentfault
這三種類型大同小異,AtomicIntegerArray對應AtomicInteger,AtomicLongArray對應AtomicLong,AtomicReferenceArray對應AtomicReference。api
其實閱讀源碼也能夠發現,這些數組原子類與對應的普通原子類相比,只是多了經過索引找到內存中元素地址的操做而已。數組
注意:原子數組並非說可讓線程以原子方式一次性地操做數組中全部元素的數組。
而是指對於數組中的每一個元素,能夠以原子方式進行操做。
說得簡單點,原子數組類型其實能夠當作原子類型組成的數組。oracle
好比:性能
AtomicIntegerArray array = new AtomicIntegerArray(10); array.getAndIncrement(0); // 將第0個元素原子地增長1
等同於spa
AtomicInteger[] array = new AtomicInteger[10]; array[0].getAndIncrement(); // 將第0個元素原子地增長1
本節將以AtomicIntegerArray爲例,介紹下原子數組的原理,AtomicLongArray和AtomicReferenceArray的使用和源碼與AtomicIntegerArray大同小異,讀者能夠本身查看Oracle官方文檔和源碼。線程
AtomicIntegerArray其實和其它原子類區別並不大,只不過構造的時候傳入的是一個int[]數組,而後底層經過Unsafe類操做數組:
3d
能夠看到,AtomicIntegerArray提供了兩種構造器,本質都是內部利用array變量保存一個int[]數組引用。 code
另外,AtomicIntegerArray利用Unsafe類直接操做int[]對象的內存地址,以達到操做數組元素的目的,幾個關鍵的變量解釋以下:
int base = unsafe.arrayBaseOffset(int[].class);
Unsafe類的arraBaseOffset方法:返回指定類型數組的第一個元素地址相對於數組起始地址的偏移值。
int scale = unsafe.arrayIndexScale(int[].class);
Unsafe類的arrayIndexScale方法:返回指定類型數組的元素所佔用的字節數。好比int[]數組中的每一個int元素佔用4個字節,就返回4。
那麼,經過base + i * sacle
其實就能夠知道 索引i的元素在數組中的內存起始地址。
可是,觀察AtomicIntegerArray的byteOffset方法,是經過i << shift + base
的公式計算元素的起始地址的:
$$ i << shift + base = i * 2^{shift} + base $$
這裏,$$ 2^{shift} $$其實就等於scale。
shift = 31 - Integer.numberOfLeadingZeros(scale)
,Integer.numberOfLeadingZeros(scale)
是將scale轉換爲2進制,而後從左往右數連續0的個數。
讀者能夠本身計算下:shift = 31 - Integer.numberOfLeadingZeros(4) = 31 - 29 =2
之因此要這麼繞一圈,實際上是處於性能的考慮,經過移位計算乘法的效率每每更高。