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