并发编程原子类完整教程:Java并发编程之原子操作类实战教程
AtomicLongArray 用于原子的更新long类型数组的值,即以整个数组对象为单位,数组元素可以执行原子性的操作。该类是JUC原子包(java.util.concurrent.atomic.AtomicLongArray)中的数组类。AtomicLong 是以 volatile+CAS 方式来实现原子操作的,AtomicLongArray 使用的是 final关键字在多线程环境下的语义(如果把数组定义为volatile类型,其里面的数组元素在读写方面是没有volatile语义的)。JDK官方提供了3个原子数组,它们提供了通过原子的方式更新数组里的某个元素的能力,主要借助 Unsafe 类实现其核心功能。
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素。
AtomicLongArray有以下特点:
- 可以存放long数值的原子性数组
- 以整个数组对象为单位,里面的元素操作都是原子性的
1、AtomicLongArray主要方法
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray这3个类提供的方法几乎一模一样,以 AtomicLongArray 类为例,它主要是提供原子的方式更新数组里的长整型,其常用方法如下:
1.1 构造方法
AtomicLongArray 提供了两个构造函数,一个是根据参数length来new一个长度为length的数组,另外一个根据传入的数组参数将此参数数组赋值给变量array。示例如下:
//构造方法创建长度为length的数组,并且赋值给数组元素array
public AtomicLongArray(int length) {
array = new long[length];
}
//将参数数组赋值给元素变量数组
public AtomicLongArray(long[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
1.2 主要方法
// 将位置i处的元素设置为给定值。
public void set(int i, long newValue)
// 获取位置i的当前值。
public long get(int i)
// 最终将位置i的元素设置为给定值。
public void lazySet(int i, long newValue)
// 以原子方式将给定值添加到索引i处的元素,然后返回变化后的值。
public long addAndGet(int i, long delta)
// 以原子方式将给定值添加到索引i处的元素,然后返回变化前的值。
public long getAndAdd(int i, long delta)
// 原子地将索引i处的元素减1,然后返回变化后的值。
public long decrementAndGet(int i)
// 以原子方式将索引i处的元素减1,然后返回变化前的值。
public long getAndDecrement(int i)
// 以原子方式将索引i处的元素增加1,然后返回变化后的值。
public long incrementAndGet(int i)
// 以原子方式将索引i处的元素增加1,然后返回变化前的值。
public long getAndIncrement(int i)
// 以原子方式将位置i处的元素设置为给定值并返回旧值。
public long getAndSet(int i, long newValue)
// 返回数组的长度。
public int length()
// 返回数组当前值的String表示形式。
public String toString()
// 如果当前值==期望值,则以原子方式将位置i处的元素设置为给定的更新值。
public boolean compareAndSet(int i, long expect, long update)
// 如果当前值==期望值,则以原子方式将位置i处的元素设置为给定的更新值。
public boolean weakCompareAndSet(int i, long expect, long update)
2、AtomicLongArray演示示例
2.1 演示示例1
import java.util.concurrent.atomic.AtomicLongArray;
public class AtomicLongArrayTest {
public static void main(String[] args) {
//1、创建给定长度的新 AtomicIntegerArray。
AtomicLongArray atomicLongArray = new AtomicLongArray(10);
//2、将位置 i 的元素设置为给定值,默认值为0
atomicLongArray.set(9,10);
System.out.println("Value: " + atomicLongArray.get(9) + "默认值:" + atomicLongArray.get(0));
//3、返回该数组的长度
System.out.println("数组长度:" + atomicLongArray1.length());
// 4、addAndGet()方法:以原子方式先对给定下标加上特定的值,再获取相加后的值
AtomicLongArray atomicLongArray2 = new AtomicLongArray(10);
atomicLongArray2.set(5,10);
System.out.println("Value: " + atomicLongArray2.get(5));
atomicLongArray2.addAndGet(5,10);
System.out.println("Value: " + atomicLongArray2.get(5));
// 5、compareAndSet()方法:如果当前值 == 预期值,则以原子方式将位置 i 的元素设置为给定的更新值。
AtomicLongArray atomicLongArray3 = new AtomicLongArray(10);
atomicLongArray3.set(5,10);
System.out.println("当前值: " + atomicLongArray3.get(5));
Boolean bool = atomicLongArray3.compareAndSet(5,10,30);
System.out.println("结果值: " + atomicLongArray3.get(5) + " Result: " + bool);
// 6、decrementAndGet()方法:以原子方式先将当前下标的值减1,再获取减1后的结果
AtomicLongArray atomicLongArray4 = new AtomicLongArray(10);
atomicLongArray4.set(5,10);
System.out.println("下标为5的值为:" + atomicLongArray4.get(5));
Long result1 = atomicLongArray4.decrementAndGet(5);
System.out.println("result1的值为:" + result1);
System.out.println("下标为5的值为:" + atomicLongArray4.get(5));
// 7、getAndAdd()方法:以原子方式先获取当前下标的值,再将当前下标的值加上给定的值
AtomicLongArray atomicLongArray5 = new AtomicLongArray(10);
atomicLongArray5.set(5,10);
Long result2 = atomicLongArray5.getAndAdd(5,5);
System.out.println("result2的值为:" + result2);
System.out.println("下标为5的值为:" + atomicLongArray5.get(5));
// 8、getAndDecrement()方法:以原子方式先获取当前下标的值,再对当前下标的值减1
AtomicLongArray atomicLongArray6 = new AtomicLongArray(10);
atomicLongArray6.set(1,10);
System.out.println("下标为1的值为:" + atomicLongArray6.get(1));
Long result3 = atomicLongArray6.getAndDecrement(1);
System.out.println("result3的值为:" + result3);
System.out.println("下标为1的值为:" + atomicLongArray6.get(1));
// 9、getAndIncrement()方法:以原子方式先获取当前下标的值,再对当前下标的值加1
AtomicLongArray atomicLongArray7 = new AtomicLongArray(10);
atomicLongArray7.set(2,10);
System.out.println("下标为2的值为:" + atomicLongArray7.get(2));
Long result4 = atomicLongArray7.getAndIncrement(2);
System.out.println("result4的值为:" + result4);
System.out.println("下标为2的值为:" + atomicLongArray7.get(2));
// 10、getAndSet()方法:将位置 i 的元素以原子方式设置为给定值,并返回旧值。
AtomicLongArray atomicLongArray8 = new AtomicLongArray(10);
atomicLongArray8.set(3,10);
System.out.println("下标为3的值为:" + atomicLongArray8.get(3));
Long result5 = atomicLongArray8.getAndSet(3,50);
System.out.println("result5的值为:" + result5);
System.out.println("下标为3的值为:" + atomicLongArray8.get(3));
// 11、incrementAndGet()方法: 以原子方式先对下标加1再获取值
AtomicLongArray atomicLongArray9 = new AtomicLongArray(10);
atomicLongArray9.set(4,10);
System.out.println("下标为4的值为:" + atomicLongArray9.get(4));
Long result6 = atomicLongArray9.incrementAndGet(4);
System.out.println("result6的值为:" + result6);
System.out.println("下标为4的值为:" + atomicLongArray9.get(4));
}
}
2.2 演示示例2
TestThread 代码显示了在多线程环境中 AtomicLongArray 的用法。
import java.util.concurrent.atomic.AtomicLongArray;
public class TestThread {
private static AtomicLongArray atomicLongArray = new AtomicLongArray(10);
public static void main(final String[] arguments) throws InterruptedException {
for (int i = 0; i<atomicLongArray.length(); i++) {
atomicLongArray.set(i, 1);
}
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Values: ");
for (int i = 0; i<atomicLongArray.length(); i++) {
System.out.print(atomicLongArray.get(i) + " ");
}
}
static class Increment implements Runnable {
public void run() {
for(int i = 0; i<atomicLongArray.length(); i++) {
long add = atomicLongArray.incrementAndGet(i);
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: "+ add);
}
}
}
static class Compare implements Runnable {
public void run() {
for(int i = 0; i<atomicLongArray.length(); i++) {
boolean swapped = atomicLongArray.compareAndSet(i, 2, 3);
if(swapped) {
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: 3");
}
}
}
}
}
上述代码是在2个线程中同时处理 AtomicLongArray 对象,并达到了预期的效果。
Thread 9, index 0, value: 2
Thread 10, index 0, value: 3
Thread 9, index 1, value: 2
Thread 9, index 2, value: 2
Thread 9, index 3, value: 2
Thread 9, index 4, value: 2
Thread 10, index 1, value: 3
Thread 9, index 5, value: 2
Thread 10, index 2, value: 3
Thread 9, index 6, value: 2
Thread 10, index 3, value: 3
Thread 9, index 7, value: 2
Thread 10, index 4, value: 3
Thread 9, index 8, value: 2
Thread 9, index 9, value: 2
Thread 10, index 5, value: 3
Thread 10, index 6, value: 3
Thread 10, index 7, value: 3
Thread 10, index 8, value: 3
Thread 10, index 9, value: 3
Values:
3 3 3 3 3 3 3 3 3 3
3、源码分析
AtomicLongArray 的代码很简单,下面对AtomicLong部分源码进行解析。
3.1 实现基础 - 主要字段
AtomicLong 是依赖 volatile 关键字和 CAS 指令实现的,AtomicLongArray 则不同,它的实现主要依赖于三个变量:
private static final long serialVersionUID = -2308431214976778248L;
// Unsafe是所有并发工具的基础工具类
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 数组对象头到数组首元素间的地址偏移量
private static final int base = unsafe.arrayBaseOffset(long[].class);
// 数组中相邻元素的地址偏移量的位移表示形式(若shift=x,那么相邻元素的偏移量就是"1<<shift")
private static final int shift;
// 实际存放元素的数组
private final long[] array;
- base:base变量表示内存中long[]的初始地址。
- shift:long[]数组中元素位移个数。
- array:用来存储真实long[]数组的变量。
- arrayBaseOffset和arrayIndexScale是Unsafe类提供的native方法。
静态块主要是对静态常量shift初始化。
static {
// 表示相邻元素的地址偏移量
int scale = unsafe.arrayIndexScale(long[].class);
if((scale & (scale - 1)) != 0){
throw new Error("data type of scale not a power of two");
}
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
- scale表示相邻元素的地址偏移量,因为内存对齐的原因,scale一定是2n。
- shift是相邻元素的地址偏移量的位移表示形式,Integer.numberOfLeadingZeros(scale) 取scale进制前导零的个数,所以 shift 是 scale 二进制有效位数减1,即有效的0的个数。如scale=16,即scale=0b00000000_00000000_00000000_00010000 , 那么shift=4。
3.2 实现基础 - 构造方法
构造方法有以下2个:
public AtomicLongArray(int length);
public AtomicLongArray(long[] array);
AtomicLongArray(int)创建指定长度的原子数组.
- 以 int length 作为参数的构造方法会初始化一个length长度的long[],默认值均为0
AtomicIntegerArray(long[])利用克隆(这里是深度克隆,源数组和原子数组互不影响)将指定的普通数组包装成原子数组。
- long[] array作为参数的构造方法使用参数array作为内部数组。
public AtomicLongArray(int length){
array = new long[length];
}
public AtomicLongArray(long[] array){
this.array = array.clone();
}
3.3 实现基础 - 主要方法
3.3.1 工具方法
checkedByteOffset方法检查数组是否越界。
/**
* 获取数组array的第i个元素相对AtomicLongArray的内存偏移量
* base为数组首地址相对AtomicLongArray的内存偏移量
* array数组中第i个元素相对AtomicLongArray的内存偏移量计算方法 = i * scale + base,其中scale为上面介绍的数组中一个元素的大小
* 由于位移方法效率比乘法效率快,乘法转换成位移计算方式 = i << shift + base,其中shift为上面介绍的scale中最大的1所在的位数
*/
private static long byteOffset(int i){
return ((long) i << shit) + base;
}
/**
* 该函数调用byteOffset就是为了获取数组array的第i个元素相对AtomicIntegerArray的内存偏移量
*/
private long checkedByteOffset(int i){
if(i < 0 || i >= array.length){
throw new IndexOutOfBoundsException("index " + i);
}
return byteOffset(i);
}
3.3.2 get和set方法
可以通过set()方法设置指定元素的值,通过get()方法获取指定元素的值。
private long getRaw(long offset){
unsafe.getLongVolatile(array, offset);
}
public final long get(int i){
return getRaw(checkedByteOffset(i));
}
public final void set(int i, long newValue){
unsafe.putLongVolatile(array, checkedByteOffset(i), newValue);
}
可以看到,array 虽然没有使用 volatile,但它调用的都是 unsafe 里面具有 volatile 语义的方法,也就是通过内存下地址对数组元素的操作,也是具有volatile语义的,即具有可见性。
3.3.3 lazySet方法
public final void lazySet(int i, long newValue){
unsafe.putOrderedLong(array, checkedByteOffset(i), newValue);
}
lazySet与set实现的功能类似,二者区别如下:
- lazySet实现的是最终一致性,set实现的是强一致性
- lazySet:多线程并发时,线程A调用unsafe.putOrderedInt更新newValue值时,只是把线程A内存中的元素更新成功了,但其它线程此时看不到线程A更新的newValue值,需过一会,线程A更新的newValue才会刷入到主内存中,此时其它线程才能看到线程A更新的值
- set: 多线程并发时,线程A更新的值会立即刷入主内存中
- 总结:对于数据一致性要求高的建议用set
3.3.4 getAndSet方法
getAndSet方法为获取设置方法,即先获取数组array的第i号元素的值,然后把newValue更新到第i号元素中。
public final long getAndSet(int i, long newValue){
return unsafe.getAndSetLong(array, checkedByteOffset(i), newValue);
}
3.3.5 比较设置方法
比较设置方法有 compareAndSet 和 weakCompareAndSet。
- compareAndSet()方法用于设置指定元素的值,如果当前值与期望值相等则更新为新的值,这个方法是支持CAS操作的,同一个时间只有一个线程调用 compareAndSet()方法。
- compareAndSet()方法通过乐观锁的方式把update更新到数组array的第i号元素中,其中expect为读取到的数组第i号元素的的值,通过乐观锁的方式,先比较expect是否与第i号元素的值相等,如果相等,把update更新到第i号元素
- weakCompareAndSet() 方法与 compareAndSet() 类似,但 weakCompareAndSet() 不会插入内存屏障,不能保障volatile的原子性
private boolean compareAndSetRaw(long offset, long expect, long update){
return unsafe.compareAndSwapLong(array, offset, expect, update);
}
public final boolean compareAndSet(int i, long expect, long update){
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
public final boolean weakCompareAndSet(int i, long expect, long update){
return compareAndSet(i, expect, update);
}
unsafe是通过Unsafe.getUnsafe()返回的一个Unsafe对象。通过Unsafe的CAS函数对long型数组的元素进行原子操作。如compareAndSetRaw()就是调用Unsafe的CAS函数。
3.3.6 获取增加方法
获取增加方法有 getAndAdd 和 addAndGet。
- getAndAdd() 和 addAndGet()方法同时包含了给指定的元素增加值,同时返回新值2个操作。
- getAndAdd() 和 addAndGet()方法比较相似,唯一不同的是 getAndAdd() 返回增加之前的值,addAndGet() 返回增加之后的值
public final long getAndAdd(int i, long delta){
return unsafe.getAndAddLong(array, checkedByteOffset(i), delta);
}
public long addAndGet(int i, long delta){
return getAndAdd(i, delta) + delta;
}
3.3.7 获取自增方法
获取自增方法包括 getAndIncreament 和 increamentAndGet。
- incrementAndGet():先将第i号元素的值加1再更新到第i号元素中,然后返回第i号元素的值,即先加1再返回
- getAndIncrement():先获取数组第i号元素的值,然后第i号元素的值加1再更新到第i号元素中,即先返回再加1
public final long getAndIncreament(int i){
return getAndAdd(i, 1);
}
/**
* 先对数组第i号元素加1,然后返回新更新后的值
* 调用getAndAdd(i, 1),先获取了第i号元素的值,然后对第i号元素值加1更新到第i号元素中
* 先获取的第i号元素值还是尚未更新的,然后执行了{getAndAdd(i, 1)} + 1,所以最后返回的是加1后的值
*/
public final long increamentAndGet(int i){
return getAndAdd(i, 1) + 1;
}
3.3.8 获取自减方法
获取自减方法包括 getAndDecrement 和 decrementAndGet。
- decrementAndGet()方法是给定索引对应的元素减1后返回值
- getAndDecrement()和decrementAndGet()方法几乎一样,唯一不同的是 getAndDecrement() 返回的是减1之前的值
public final long getAndDecrement(int i){
return getAndAdd(i, -1);
}
public final long decrementAndGet(int i){
return getAndAdd(i, -1) - 1;
}
3.3.9 获取更新方法
获取更新方法包括 getAndUpdate 和 updateAndGet。
- getAndUpdate():先获取数组array第i号元素的值,然后第i号元素的值经过updateFunction函数处理后的值更新到第i号元素中
- updateAndGet():先把数组第i号元素的值经过updateFunction处理后的值更新到第i元素中,然后返回更新后的值
public final long getAndUpdate(int i, LongUnaryOperator updateFunction){
long offset = checkedByteOffset(i);
long prev, next;
do{
prev = getRaw(offset);
next = updateFunction.applyAsLong(prev);
}while(!compareAndSetRaw(offset, prev, next));
return prev;
}
public final long updateAndGet(int i, LongUnaryOperator updateFunction){
long offset = checkedByteOffset(i);
long prev, next;
do{
prev = getRaw(offset);
next = updateFunction.applyAsLong(prev);
}while(!compareAndSetRaw(offset, prev, next));
return next;
}
3.3.10 获取累加方法
获取累加方法包括 getAndAccumulate 和 accumulateAndGet。
- getAndAccumulate():先获取第i号元素的值,然后把输入的x经过updateFunction函数处理后的值更新到第i号元素中
- accumulateAndGet():先把输入的x经过accumulatorFunction函数出来了后的值更新到第i号元素中,然后返回更新后的值
public final long getAndAccumulate(int i, long x, LongBinaryOperator accumulatorFunction){
long offset = checkedByteOffset(i);
long prev, next;
do{
prev = getRaw(offset);
next = accumulatorFunction.applyAsLong(prev, x);
}while(!compareAndSetRaw(offset, prev, next));
return prev;
}
public final long accumulateAndGet(int i, long x, LongBinaryOperator accumulatorFunction){
long offset = checkedByteOffset(i);
long prev, next;
do{
prev = getRaw(offset);
next = accumulatorFunction.applyAsLong(prev, x);
}while(!compareAndRawSet(offset, prev, next));
return next;
}
评论区