字段更新器的使用&LongAdder原子累加器

字段更新器,主要是用来更新自定义类的字段的。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);
    }
}
  1. cells 为 null(就说明还没有发生竞争),就会对 base 变量做 CAS 操作;CAS 操作成功则方法结束,否则就会调用 longAccumulate 方法。
  2. cells 不为 null,先取出当前线程所对应的累加单元,如果这个累加单元不为 null,就会调用这个累加单元的 CAS 方法,如果 CAS 执行失败(失败是因为其它线程已经操作过,这个累加单元)则会调用 longAccumulate 方法;如果这个累加单元为 null,则会直接执行 longAccumulate 方法。

下面是当前线程获取累加单元的逻辑:

static final int getProbe() {
    return UNSAFE.getInt(Thread.currentThread(), PROBE);
}

该方法主要是用来获取当前线程的随机探针。

参考资料

LongAdder源码分析

源码阅读:全方位讲解LongAdder

字段更新器的使用&LongAdder原子累加器

上一篇:git常用命令


下一篇:Kali之aircrack-ng