侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 292 篇文章
  • 累计创建 132 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Java并发编程之原子操作类AtomicLongArray实战详解

孔子说JAVA
2022-06-08 / 0 评论 / 0 点赞 / 61 阅读 / 12,667 字 / 正在检测是否收录...

并发编程原子类完整教程: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;
}
0

评论区