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

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

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

目 录CONTENT

文章目录

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

孔子说JAVA
2022-06-06 / 0 评论 / 0 点赞 / 85 阅读 / 11,941 字 / 正在检测是否收录...

并发编程原子类完整教程:Java并发编程之原子操作类实战教程

AtomicIntegerArray 用于原子的更新int类型数组的值,即以整个数组对象为单位,数组元素可以执行原子性的操作。该类是JUC原子包(java.util.concurrent.atomic.AtomicIntegerArray)中的数组类。AtomicInteger 是以 volatile+CAS 方式来实现原子操作的,AtomicIntegerArray 使用的是 final关键字在多线程环境下的语义(如果把数组定义为volatile类型,其里面的数组元素在读写方面是没有volatile语义的)。JDK官方提供了3个原子数组,它们提供了通过原子的方式更新数组里的某个元素的能力,主要借助 Unsafe 类实现其核心功能。

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

1、AtomicIntegerArray主要方法

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray这3个类提供的方法几乎一模一样,以 AtomicIntegerArray 类为例,它主要是提供原子的方式更新数组里的整型,其常用方法如下:

// 创建给定 length 长度的新 AtomicIntegerArray。
AtomicIntegerArray(int length)

// 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。
AtomicIntegerArray(int[] array)

// 返回值与函数名字相关,比如get在前就返回原始值再进行加减设置等操作,get在后则先进行操作再返回更新后的值

// 以原子方式将给定值添加到索引 i (从0开始)的元素。
int addAndGet(int i, int delta)

// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet(int i, int expect, int update)

// 以原子方式将索引 i 的元素减1。
int decrementAndGet(int i)

// 获取位置 i 的当前值。
int get(int i)

// 以原子方式将给定值与索引 i 的元素相加。
int getAndAdd(int i, int delta)

// 以原子方式将索引 i 的元素减 1。
int getAndDecrement(int i)

// 以原子方式将索引 i 的元素加 1。
int getAndIncrement(int i)

// 以原子方式将位置 i 的元素设置为给定值,并返回旧值。
int getAndSet(int i, int newValue)

// 以原子方式将索引 i 的元素加1。
int incrementAndGet(int i)

// 最终将位置 i 的元素设置为给定值。
void lazySet(int i, int newValue)

// 返回该数组的长度。
int length()

// 将位置 i 的元素设置为给定值。
void set(int i, int newValue)

// 返回数组当前值的字符串表示形式。
String toString()

// 如果位置 i 的元素当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(int i, int expect, int update)

2、AtomicIntegerArray演示示例

2.1 演示示例1

  1. 新建一个Thread类,用来模拟多线程环境:
package com.securitit.serialize.atomics;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayThread extends Thread {
	// 自定义线程名称.
	private String threadName;
	// AtomicIntegerArray实例.
	private AtomicIntegerArray atomicIntegerArray;
	// CountDownLatch可以进行线程计数,到达指定计数时,可唤醒调用await方法的线程.
	private CountDownLatch countDownLatch;

	public AtomicIntegerArrayThread(String threadName, AtomicIntegerArray arr, CountDownLatch countDownLatch) {
		this.threadName = threadName;
		this.atomicIntegerArray = arr;
		this.countDownLatch = countDownLatch;
	}

	@Override
	public void run() {
		for (int index = 0; index < atomicIntegerArray.length(); index++) {
			atomicIntegerArray.incrementAndGet(index);
		}
		countDownLatch.countDown();
	}

}
  1. 对AtomicIntegerArray变量进行修改

新建一个测试类,在其中创建一个长度为100的 AtomicIntegerArray 数组变量,初始化各个位置值为当前索引值,然后开启四个线程对 AtomicIntegerArray 变量进行修改:

package com.securitit.serialize.atomics;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTester {
	// AtomicIntegerArray实例.
	private static AtomicIntegerArray atomicIntegerArray;
	// CountDownLatch可以进行线程计数,到达指定计数时,可唤醒调用await方法的线程.
	private static CountDownLatch countDownLatch = new CountDownLatch(4);

	public static void main(String[] args) throws Exception {
		atomicIntegerArray = new AtomicIntegerArray(100);
		// 初始化数组内部值.
		for (int index = 0; index < atomicIntegerArray.length(); index++) {
			// 用当前索引作为值.
			atomicIntegerArray.set(index, index);
		}
		// 启动多线程测试.
		new AtomicIntegerArrayThread("A", atomicIntegerArray, countDownLatch).start();
		new AtomicIntegerArrayThread("B", atomicIntegerArray, countDownLatch).start();
		new AtomicIntegerArrayThread("C", atomicIntegerArray, countDownLatch).start();
		new AtomicIntegerArrayThread("D", atomicIntegerArray, countDownLatch).start();
		// 等待所有线程执行完毕.
		countDownLatch.await();
		// 输出经过计算的数值.
		for (int index = 0; index < atomicIntegerArray.length(); index++) {
			System.out.println(atomicIntegerArray.get(index));
			if (atomicIntegerArray.get(index) != index + 4) {
				System.out.println("数据计算出现问题.");
			}
		}
	}
}

运行代码后的输出结果为空,这充分说明了经过多线程计算的结果与我们预期的结果相同,证明了在多线程环境下,AtomicIntegerArray 的确可以保证int数组元素操作的原子性。

2.2 演示示例2

class AtomicIntArrayDemo {

     public static void main(String[] args) {
         AtomicIntegerArray array = new AtomicIntegerArray(new int[]{45,23,13,47,12,42});
         for (int i = 0; i <array.length() ; i++) {
             final int j=i;
             new Thread(()->{
                 array.compareAndSet(j, 13, 31);
                 array.getAndAdd(j, 2);
                 array.decrementAndGet(j);
                 array.getAndSet(j, array.get(j) - 1);
             }).start();

         }
         System.out.println(array);
     }
}

2.3 AtomicIntegerArray的测试类

import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.IntBinaryOperator;
import java.util.function.IntUnaryOperator;
 
import org.junit.Test;
 
/**
 * AtomicIntegerArray的测试类
 */
public class AtomicIntegerArrayTest {
   /**
    * 通过size初始化AtomicIntegerArray数组类
    * @throws
    */
   @Test
   public void testConstruct0()throws Exception{
      AtomicIntegerArray testObj=new AtomicIntegerArray(2);
      System.out.println(testObj.toString());
   }
        
   /**
    *  通过数组初始化AtomicIntegerArray数组类,其实就是将int[] array=intArr.clone();
    *  clone()方法是将intArr内容复制到一个新的相同大小的数组空间,
    * 并将地址赋给array
    * @throws
    */
   @Test
   public void testConstruct1()throws Exception{
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.toString());
   }
   
   /**
    * 数组的长度
    * @throws
    */
   @Test
   public void testLength(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.length());
   }
   
   /**
    * 获取数组下标的内容,需要检查是否越界
    * @throws
    */
   @Test
   public void testGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.get(2));
   }
   
   /**
    * 设置数组下标的内容为新的值,unsafe.putIntVolatile设置值
    * @throws
    */
   @Test
   public void testSet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      testObj.set(2,23);
      System.out.println(testObj.get(2));
   }
   
   /**
    * 设置数组下标的内容为新的值,unsafe.putOrderedInt
    * @throws
    */
   @Test
   public void testLazySet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      testObj.lazySet(2,23);
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标的值,并设置成新的值
    * @throws
    */
   @Test
   public void testGetAndSet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.getAndSet(2,23));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 如果下标的值为expect的值,则更新成新值,返回true。否则不更新,返回false
    * @throws
    */
   @Test
   public void testCompareAndSet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.compareAndSet(2,3,23));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 如果下标的值为expect的值,则更新成新值,返回true。否则不更新,返回false
    * @throws
    */
   @Test
   public void testWeakCompareAndSet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.weakCompareAndSet(2,3,23));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标的值,并将值加一
    * @throws
    */
   @Test
   public void testGetAndIncrement(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.getAndIncrement(2));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标的值,并将值减一
    * @throws
    */
   @Test
   public void testGetAndDecrement(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.getAndDecrement(2));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标的值,并将值加一
    * @throws
    */
   @Test
   public void testGetAndAdd(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.getAndAdd(2,23));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 将值加一,并返回下标的值
    * @throws
    */
   @Test
   public void testIncrementAndGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.incrementAndGet(2));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 将值减一,并返回下标的值
    * @throws
    */
   @Test
   public void testDecrementAndGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.decrementAndGet(2));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 将值加一,并返回下标的值
    * @throws
    */
   @Test
   public void testAddAndGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.addAndGet(2,23));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标的值,并更新成新值
    * @throws
    */
   @Test
   public void testGetAndUpdate(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      IntUnaryOperator operator=new IntUnaryOperator() {
          @Override
          public int applyAsInt(int operand) {
              return 33;
          }
      };
      System.out.println(testObj.getAndUpdate(2,operator));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 更新成新值并返回下标的值
    * @throws
    */
   @Test
   public void testUpdateAndGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      IntUnaryOperator operator=new IntUnaryOperator() {
          @Override
          public int applyAsInt(int operand) {
             return 33;
          }
      };
      System.out.println(testObj.updateAndGet(2,operator));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 返回下标值,并更新成新值
    * @throws
    */
   @Test
   public void testGetAndAccumulate(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      IntBinaryOperator operator=new IntBinaryOperator() {
          @Override
          public int applyAsInt(int left, int right) {
              return 332;
          }
      };
      System.out.println(testObj.getAndAccumulate(2,23,operator));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 更新成新值并返回下标值
    * @throws
    */
   @Test
   public void testAccumulateAndGet(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      IntBinaryOperator operator=new IntBinaryOperator() {
          @Override
          public int applyAsInt(int left, int right) {
              return 332;
          }
      };
      System.out.println(testObj.accumulateAndGet(2,23,operator));
      System.out.println(testObj.get(2));
   }
   
   /**
    * 拼接数组的值,StringBuilder
    * @throws
    */
   @Test
   public void testToString(){
      int[] intArr={1,2,3};
      AtomicIntegerArray testObj=new AtomicIntegerArray(intArr);
      System.out.println(testObj.toString());
   }
}

3、源码分析

本教程的源码分析以JDK1.8中 AtomicIntegerArray 的为例。

3.1 实现基础 - 主要字段

AtomicBoolean 和 AtomicInteger 都是依赖 volatile 关键字和 CAS 指令实现的,AtomicIntegerArray 则不同,它的实现主要依赖于三个变量:

// Unsafe是所有并发工具的基础工具类
private static final Unsafe unsafe = Unsafe.getUnsafe();

// 数组对象头到数组首元素间的地址偏移量
private static final int base = unsafe.arrayBaseOffset(int[].class);

// 数组中相邻元素的地址偏移量的位移表示形式(若shift=x,那么相邻元素的偏移量就是"1<<shift")
private static final int shift;

// 实际存放元素的数组
private final int[] array;
  • base:base变量表示内存中int[]的初始地址。
  • shift:int[]数组中元素位移个数。
  • array:用来存储真实int[]数组的变量。
  • arrayBaseOffset和arrayIndexScale功能如上所说,是Unsafe类提供的native方法。

静态块主要是对静态常量shift初始化。

static {
   //表示相邻元素的地址偏移量
   int scale = unsafe.arrayIndexScale(int[].class);
   if ((scale & (scale - 1)) != 0)
      throw new Error("data type scale not a power of two"); // 非法的,scale一定是2的幂次方
   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 AtomicIntegerArray(int length);

public AtomicIntegerArray(int[] array);

AtomicIntegerArray(int)创建指定长度的原子数组.

  • int length 作为参数的构造方法会初始化一个length长度的int[],默认值均为0

AtomicIntegerArray(int[])利用克隆(这里是深度克隆,源数组和原子数组互不影响)将指定的普通数组包装成原子数组。

  • int[] array作为参数的构造方法使用参数array作为内部数组。
public AtomicIntegerArray(int length) {
  array = new int[length];
}

public AtomicIntegerArray(int[] array) {
  // Visibility guaranteed by final field guarantees
  this.array = array.clone();
}

3.3 实现基础 - 主要方法

3.3.1 获取指定的下标元素 get(int)

get()方法主要利用 byteOffset 根据公式 offset=base+i*scale 求出数组对象的起始位置下标元素的偏移量offset,然后利用 unsafe.getIntVolatile 根据公式 indexAddr=arrayAddr+offset 求出出指定偏移位置的元素值。

public final int get(int i) {
   return getRaw(checkedByteOffset(i));
}

private long checkedByteOffset(int i) {
   //下标检查,并计算数组对象起始位置至此下标元素的偏移量
   if (i < 0 || i >= array.length)
     throw new IndexOutOfBoundsException("index " + i);
   return byteOffset(i);
}

private static long byteOffset(int i) {//计算偏移量
   // i*2^shift +base 即,"i*元素间距+对象头长度"
   return ((long) i << shift) + base; 
}

private int getRaw(long offset) {
   //利用unsafe方法求出指定偏移位置的int值
   return unsafe.getIntVolatile(array, offset);
}

3.3.2 更新指定下标元素set(int,int)

set方法与get方法类似,都是先调用 checkedByteOffset 计算偏移量 offset,不过此set方法要调用 putIntVolatile 在指定内存位置修改值而已。

public final void set(int i, int newValue) {
  unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}

3.3.3 getAndSet(int,int)

先获取值再设置新值,同样先调用 checkedByteOffset 计算偏移量offset,然后 getAndSetInt 进行CAS自旋更新指定的元素。

public final int getAndSet(int i, int newValue) {
    return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
}

public final int getAndSetInt(Object o, long offset, int newValue) {//unsafe的方法
  int v;
  do {
    v = getIntVolatile(o, offset);
  } while (!compareAndSwapInt(o, offset, v, newValue));
  return v;
}

compareAndSet(),CAS更新期望的元素,先调用 checkedByteOffset 计算偏移量offset,然后调用 compareAndSwapInt 尝试CAS更新元素。

public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

private boolean compareAndSetRaw(long offset, int expect, int update) {
    return unsafe.compareAndSwapInt(array, offset, expect, update);
}
0

评论区