字段更新器,主要是用来更新自定义类的字段的。Java 提供以下三种字段更新器:
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
public class Test5 {
private volatile int field;
public static void main(String[] args) {
AtomicIntegerFieldUpdater fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(Test5.class, "field");
Test5 test5 = new Test5();
fieldUpdater.compareAndSet(test5, 0, 10);
// 修改成功 field = 10
System.out.println(test5.field);
// 修改成功 field = 20
fieldUpdater.compareAndSet(test5, 10, 20);
System.out.println(test5.field);
// 修改失败 field = 20
fieldUpdater.compareAndSet(test5, 10, 30);
System.out.println(test5.field);
}
}
值得注意的是:字段更新器要操作哪个字段,哪个字段必须被 volatile 修饰。
LongAdder 原子累加器
原子累加器顾名思义就是用来计数的,Java 中可以使用 LongAdder 和 AtomicLong/AtomicInteger。
AtomicLong/AtomicInteger 的实现方式中,内部会维护一个 value 变量用来存放实际的值(自增、自减等都是操作这个变量),这就导致在多线程环境下,CAS 操作失败后会不断进行循环重试。也就是说,当并发量大的情况下,会出现很多次循环重试,从而降低了效率。
简单来说:AtomicLong/AtomicInteger 都通过 while 循环执行 CAS 操作的,所以说,当并发量大的情况下,会出现多次重试,从而降低效率。
而 LongAdder 是 Java 8 新增的用于并发环境的计数器,目的是为了在高并发情况下,代替 AtomicLong/AtomicInteger,成为一个用于高并发情况下的高效的通用计数器。
LongAdder 继承了 Striped64 类(支持高并发的累加器),来实现累加功能的;Striped64 的设计核心思路就是通过内部的分散计算来避免竞争。
Striped64 内部三个重要的成员变量:
/**
* 累加单元数组,懒惰初始化。
*/
transient volatile Cell[] cells;
/**
* 基础值,
* 1. 在没有竞争时会更新这个值;
* 2. 在 cells 初始化的过程中,cells 处于不可用的状态,这时候也会尝试将通过 cas 操作值累加到 base。
*/
transient volatile long base;
/**
* CAS 锁。在 Cell 扩容的时候,会设置为 1。
*/
transient volatile int cellsBusy;
LongAdder 的核心逻辑是在 add 方法中:
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
// 这里执行 as == null 是为了防止 as.length 出现空指针异常。
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
- cells 为 null(就说明还没有发生竞争),就会对 base 变量做 CAS 操作;CAS 操作成功则方法结束,否则就会调用 longAccumulate 方法。
- cells 不为 null,先取出当前线程所对应的累加单元,如果这个累加单元不为 null,就会调用这个累加单元的 CAS 方法,如果 CAS 执行失败(失败是因为其它线程已经操作过,这个累加单元)则会调用 longAccumulate 方法;如果这个累加单元为 null,则会直接执行 longAccumulate 方法。
下面是当前线程获取累加单元的逻辑:
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
该方法主要是用来获取当前线程的随机探针。