并发编程原子类完整教程: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
- 新建一个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();
}
}
- 对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);
}
评论区