AtomicIntegerArray源码解析
顾名思义,AtomicIntegerArray就是JDK提供的提供原子操作能力的整型数组。之所以提供这个类是因为整型数组在多线程环境下是线程不安全的。
1 | public class UseIntegerArrayUnsafe implements Runnable { |
上述类UseIntegerArrayUnsafe,实现了Runnable接口,在其实现的run方法中,对私有数组变量的两个值分别自增1.
而后在main方法中,起了10个线程,分别执行。
AtomicIntegerArray类介绍
JDK提供了AtomicIntegerArray类用于原子的更新int类型的数组。不同于其他的Atomic×××,AtomicInterArray并没有使用CAS+Volatile关键字来实现原子操作。
类图
主要属性
1 | private static final Unsafe unsafe = Unsafe.getUnsafe(); |
AtomicIntegerArray主要有上面4个属性:
unsafe:Sun提供的CAS算法的相关操作,AtomicIntegerArray底层就是通过它来完成值的修改的;
base:数组在ArrayIntegerArray中的内存起始地址
shift:左移偏移量。用来计算数组中的元素在整个AtomicIntegerArray对象中的偏移量。数组中第i个元素的地址就是 i << shift + base.
array:数组对象,保存数值
主要方法
初始化
1 | static { |
首先看下这个静态代码块,在这个代码块中,首先定义一个int变量scale,然后赋值为int[].class占用的字节数。
如果占用的字节数不是2的幂次,抛出异常。
最后再通过scale计算出来左移的偏移量。
例如,int一般占用4个字节,那么scale就等于4,而shift就等于31减去4变为二进制之后的前导0的个数,也就是2.
那么,数组第一个位置的元素就是base + 0 << 2, A[base],第二个就是base + 1 << 2.即A[base + 4],第三个就是A[base + 8]
checkedByteOffset(int)
1 | private long checkedByteOffset(int i) { |
校验索引i是否越界,越界抛出异常,否则调用byteOffset方法计算出偏移量返回。
byteOffset(int)
1 | private static long byteOffset(int i) { |
将索引i左移shift位,加上base返回目标位置的内存地址
AtomicIntegerArray(int)
1 | /** |
创建一个指定数组长度的AtomicIntegerArray对象
AtomicIntegerArray(int[])
1 | /** |
根据指定的数组,创建一个AtomicIntegerArray对象
length()
1 | /** |
返回数组的长度
get(int)
1 | /** |
获取数组指定位置的值。调用checkedByteOffset检查越界情况,并在方法内部调用byteOffset计算出位置i的内存偏移量。传给getRaw方法,获取值
getRaw(long offset)
1 | private int getRaw(long offset) { |
直接调用unsafe的方法,获取对应内存偏移量上的值
set(int , int)
1 | /** |
调用unsafe的方法直接将位置i上的值设置为给定的新值
lazySet(int, int)
1 | /** |
调用unsafe的方法直接将位置i上的值设置为给定的新值。但是这个方法有别于上面的set方法,它在一段时间内是有可能让其他线程查到原来的旧值,而不是立马查到新值。因此,它可以算是一个“最终一致性”的方法
getAndSet(int, int)
1 | /** |
返回位置i上的旧值,并将新值赋值给位置i
compareAndSet(int, int, int)
1 | /** |
如果位置i上的旧值等于期望值expect,那么就将位置i上的值赋值为update,并返回true,否则返回false
compareAndSetRaw(long, int, int)
1 | private boolean compareAndSetRaw(long offset, int expect, int update) { |
compareAndSet方法实际调用的方法,直接调用unsafe设置对应偏移位置上的值
weakCompareAndSet(int, int, int)
1 | /** |
这个方法的本意是,调用weakCompareAndSet方法时不能保证指令重排的发生,因此,这个方法有时候会毫无理由地失败。
但是从实现上看,和compareAndSet没什么区别
getAndIncrement(int)
1 | /** |
获取位置i上的值返回,并对该值自增.
getAndDecrement(int)
1 | /** |
获取位置i上的值返回,并对该值自减.
getAndAdd(int, int)
1 | /** |
返回位置i上对应的值,并加上delta
incrementAndGet(int)
1 | /** |
对位置i上的值增1,并返回
decrementAndGet(int)
1 | /** |
对位置i上的值减1,并返回
addAndGet(int, int)
1 | /** |
对位置i上的值加上delta,并返回最新的值
getAndUpdate(int, IntUnaryOperator)
1 | /** |
返回位置i上的值,并对位置i上的值应用单元函数updateFunction,更新位置i上的值。这里要求函数updateFunction是幂等的,即,多次执行结果是一致的,因为CAS操作可能失败。
updateAndGet(int, IntUnaryOperator)
1 | /** |
对位置i上的值应用单元函数updateFunction,并将应用后的值更新到位置i。要求单元函数是幂等的,因为CAS操作可能失败
getAndAccumulate(int, int, IntBinaryOperator)
1 | /** |
返回位置i上的值,并对位置i上的值应用二元函数accumulatorFunction,设置在位置i上。这里同样要求二元函数具有幂等性,因为CAS操作可能会失败。
accumulateAndGet(int, int, IntBinaryOperator)
1 | /** |
对位置i上的值应用二元函数accumulatorFunction,设置在位置i上,然后返回位置i上的值。这里同样要求二元函数具有幂等性,因为CAS操作可能会失败。
toString()
1 | /** |
返回**[value, value,value]**,这样的字符串。
应用
下面就是用AtomicIntegerArray修改在多线程环境下存在问题的代码。
1 | public class UseAtomicIntegerArray implements Runnable { |
再执行多少次都不会影响结果了。