19 引用类型的volatile替代品——原子引用类AtomicReference
Diego38 78 1

1. 前言

之前讲了 Integer、Boolean、Long 的原子更新类,本节我们学习针对任意类型的 AtomicReference 类。

2. 原子引用类 AtomicReference

AtomicReference 是替换 volatile 的,除了 volatile 的可见性功能,它还具备 CAS 更新功能。

我们看下 AtomicReference 的 API: image

除了 set/get/compareAndSet, 在 1.8 以后,AtomicReference 类还增加了 原子聚合操作 getAndAccumulate,接收一个二元函数,进行处理。

我们通过一个案例来学习下:

public class AtomicReferenceTest {

    static class UserItem {
        private String name;

        private Integer age;

        public UserItem(){

        }

        public UserItem(String name, Integer age) {
            this.age = age;
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "UserItem{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }


    public static AtomicReference<UserItem> reference = new AtomicReference();

    static Thread thread = new Thread(() -> {
        System.out.println("初始值 " + reference.get());

        UserItem userItem = new UserItem();
        userItem.setName("小明");
        userItem.setAge(10);
        reference.set(userItem);
        System.out.println("设置后的值 " + reference.get());
        reference.compareAndSet(userItem, new UserItem("小明", 11));
        System.out.println("重新赋值后 " + reference.get());
        for (int i = 0; i < 3; i++) {
            userItem.setAge(i * 10);
            reference.accumulateAndGet(userItem, (x, p) -> new UserItem(p.getName(), Math.max(p.getAge(), x.getAge())));
        }
        System.out.println("经过聚合操作后 " + reference.get());
    });

    public static void main(String[] args) {
        thread.start();
    }
}

输出如下:

初始值 null
设置后的值 UserItem{name='小明', age=10}
重新赋值后 UserItem{name='小明', age=11}
经过聚合操作后 UserItem{name='小明', age=20}

3. 原子数组类 Atomic*Array

原子数组类 Atomic*Array 有三种,分别是 AtomicIntegerArray、 AtomicLongArray、AtomicReferenceArray。

AtomicIntegerArray 的部分代码如下:

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    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 long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

从源码可以看出 AtomicIntegerArray 的操作对象是的 int 数组,compareAndSet 也好,set 也好都需要传递数组下标,意味着操作的对象是数组的元素,而不是整个数组。

我们看一个案例:

public class AtomicIntegerArrayTest {


    static int[] a = {1, 2, 3, 4, 5};
    static AtomicIntegerArray integerArray = new AtomicIntegerArray(a);

    public static void main(String[] args) {
        System.out.println("数组第一个元素: " +  integerArray.get(0));
        System.out.println("对数组第一个元素加1: " +  integerArray.incrementAndGet(0));
        System.out.println("对第二个元素做CAS: " +  integerArray.compareAndSet(1, 2, 20));
        System.out.println("对第三个元素做设值: " +  integerArray.compareAndSet(2, 3, 30));

        System.out.println("更改后整个数组:" + integerArray.toString());
    }
}

输出如下:

数组第一个元素: 1
对数组第一个元素加1: 2
对第二个元素做CAS: true
对第三个元素做设值: true
[2, 20, 30, 4, 5]

AtomicLongArray、AtomicReferenceArray 类似,只不过一个针对是 long 类型数组,一个是针对任意类型的数组。

4. 总结

原子数组类使用场景相对较少,数组一般被集合类替代,多线程场景往往使用 CopyOnWriteArrayList 或 CopyOnWriteArraySet 来替代原子数组类。

预览图
评论区

索引目录