取法其上,得乎其中

Java 原子类与 CAS:基本数据类型 / 对象引用类型 / 对象属性更新器 / 累加器

关于原子类

原子类按照我的理解就是一些能够保证并发情况下安全的一些数据类型,以及能让某些对象能够拥有原子类特性的工具。

那么它们是如何实现的呢?它们实现的思路就是使用 CAS, 全称为 compare and set,可以直接直译为比较后设置。它的主要思想可以有一个等价代码如下:

public class CompareAndSwapDemo {
    private volatile int value;
    public synchronized int compareAndSwap(int exceptedValue, int newValue) {
        int oldValue = value;
        if (exceptedValue == oldValue) {
            value = newValue;
        }
        return oldValue;
    }
}

总而言之,就是先比较自己操作时的值,然后再去内存中获取对应的值,如果此两者不相等,那么就代表有其他线程也操作了它,因此只有预期值和当前值相等的时候,才会去赋值

本质来说,这就是乐观锁的实现。因此我认为,原子类除非面对高度竞争的场景以外,它的平均性能要远优于使用锁。

而在原子类之中,有着五块类别的工具类,在下述逐个记录。

原子化的基本数据类型与数组

通过其名称Boolean、Integer就可以分辨,而数组不过是这些原子基本类型的线性表。

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • AtomicIntegerArray
  • AtomicLongArray

而此几者的用法,无非拥有线程安全特性的自增、自减、赋值,因此便不再撰述。

    public static AtomicInteger i1 = new AtomicInteger(0);
    i1.getAndIncrement(); // 自增
    i1.getAndSet(5); // 获得并设置成5,底层为CAS实现

原子化的对象引用类型

相关实现有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference。这些可以理解为是基础类型的扩展,相当于将Integer更换为了泛型,可以让某个指定类型拥有原子性

public static AtomicReference<Student> studentAtomicReference = new AtomicReference<>(new Student(0));

而 AtomicStampedReference 和 AtomicMarkableReference 只是在前者的基础上做了一些优化。

public static AtomicReference<Student> studentAtomicReference = new AtomicReference<>(new Student(0));

@AllArgsConstructor
static class Student {
    int score;
}

static class Task implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            final Student t = studentAtomicReference.get();
            Student t_new = new Student(t.score + 1);
            if (!studentAtomicReference.compareAndSet(t, t_new)) {
                i--; // 如果通过 compareAndSet 得到false,代表被其他线程修改了,因此重试一次。
            }
        }
    }
}

原子化对象属性更新器

相关实现有 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,利用它们可以原子化地更新对象的属性。

其主要使用方法为 .newUpdater(对应类.class, "要原子化的属性")

public static AtomicIntegerFieldUpdater<Student> studentScoreUpdater
        = AtomicIntegerFieldUpdater.newUpdater(Student.class, "score");

然后就可以直接同其他基本原子类一般操作。

studentScoreUpdater.getAndIncrement(student);

需要注意的是,对象属性必须是 volatile 类型的,只有这样才能保证可见性。如果对象属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。

原子化的累加器

DoubleAdder、和 LongAdder,这四个类仅仅用来执行累加操作,相比原子化的基本数据类型,速度更快,但是不支持 compareAndSet() 方法。

如果你仅仅需要累加操作,使用原子化的累加器性能会更好,但坏处是计算结果不一定准确,只能保证最终一致性。主要原因是因为其内部将累加操作分散计算到不同的容器之中,之后再进行求和操作,但在求和操作的中途可能仍会接收到累加操作。

LongAdder longAdder = new LongAdder();
longAdder.increment();

而在前两者的基础上,还提供了 LongAccumulator DoubleAccumulator 两种类型的累加器。通过它们可以实现自己定义累加规则,而这些规则可以定为任意方式的运算。

如下述创建了一个 LongAccumulator类, 而其运算规则定位了 x + y, 其中x值为我们累加器中的初始值,而y值为 .accumulate(n) 方法中的n。

LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
accumulator.accumulate(2);

因此,我们也可以设计其他的运算规则,如

  • (x, y) -> x*2 // 双倍x
  • (x, y) -> x*x // x^2
Java 原子类与 CAS:基本数据类型 / 对象引用类型 / 对象属性更新器 / 累加器

https://ku-m.cn/index.php/archives/591/

作者

KuM

发布时间

2022-07-16

许可协议

CC BY 4.0

本页的评论功能已关闭