AtomicIntegerArray

92 阅读2分钟

AtomicIntegerArray

  1. AtomicIntegerArray 的作用是什么?
  2. AtomicIntegerArray 多线程情况下是如何保证安全的?
  3. 为什么 AtomicIntegerArray 的数组要定义为 final 类型?

AtomicIntegerArray 的作用是什么?

在多线程环境下,保证数组元素运算的一致性。下面我给了一个例子,说明 AtomicIntegerArray 的作用

@Test
public void test() {
    int[] arr = new int[10];
    for (int i = 0; i < 10000; i++) {
        new Thread(()->{
            for (int j = 0; j < arr.length; j++) {
                arr[j] += 1;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 输出最终结果
    for (int i = 0; i < arr.length; i++) {
        System.out.println("Element at index " + i + ": " + arr[i]); // 会出现问题,可能某几个元素的最终结果不为 10000
    }
} 


@Test
public void testAddAndGet() {
    int[] arr = new int[10];
    AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
    for (int i = 0; i < 10000; i++) {
        new Thread(()->{
            for (int j = 0; j < arr.length; j++) {
                atomicIntegerArray.addAndGet(j,1);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 输出最终结果
    for (int i = 0; i < arr.length; i++) {
        System.out.println("Element at index " + i + ": " + atomicIntegerArray.get(i)); // 不会出现问题
    }
} 

AtomicIntegerArray 多线程情况下是如何保证安全的?

首先查看源码,可以看到在 AtomicIntegerArray 定义了一个 final 的数组,

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;

static {
    int scale = unsafe.arrayIndexScale(int[].class); 
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");
    shift = 31 - Integer.numberOfLeadingZeros(scale);   
}

private static long byteOffset(int i) {
    return ((long) i << shift) + base; // ((long) i << shift) + base 是(元素个数-1) * 类型大小,要使用 CAS 就必须知道对象在函数中的偏移量
}

由于数组元素类型没有定义成 volatile ,所以在进行实际的修改操作时,必须使用 带 volatile 的修改方法。其余的方法与 AtomicInteger 的方法类型

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

public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
    long offset = checkedByteOffset(i);
    int prev, next;
    do {
        prev = getRaw(offset);
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSetRaw(offset, prev, next));
    return next;
}

为什么 AtomicIntegerArray 的数组要定义为 final 类型?

将数组定义为 final 类型可以确保一旦初始化完成,其内容就不能再被修改。这样,当多个线程同时访问和修改 AtomicIntegerArray 时,可以确保每个线程看到的数组内容是一致的,不会因为某个线程在修改数组时导致其他线程看到不一致的状态。