本文首发于一世流云的专栏: 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
之因此要这么绕一圈,实际上是处于性能的考虑,经过移位计算乘法的效率每每更高。