第八章 原子操作类

Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰

分类:

  • 基本类型原子类

  • 数组类型原子类

  • 引用类型原子类

  • 对象的属性修改原子类

  • 原子操作增强类原理深度解析

8.1 基本类型原子类

  • AtomicInteger:整型原子类

  • AtomicBoolean:布尔型原子类

  • AtomicLong:长整型原子类

8.1.1 常用 API 简介

public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

8.1.2 Case

atomicInteger 案例演示

public class AtomicIntegerDemo {

    private static final int SIZE = 50;

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 0; i < SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        //等待上面 50 个线程全部计算完成后,再去获得最终值

        /*//暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + myNumber.atomicInteger.get());
    }
}

class MyNumber {

    AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus() {
        atomicInteger.getAndIncrement();
    }

}

8.2 数组类型原子类

  • AtomicIntegerArray:整型数组原子类

  • AtomicLongArray:长整型数组原子类

  • AtomicReferenceArray:引用类型数组原子类

8.2.1 常用 API 简介

public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

8.2.2 Case

public class AtomicIntegerArrayDemo {
    
  /**
     * 运行结果:
     * 0
     * 0
     * 0
     * 0
     * 0
     *
     * 0	1122
     * 1122	1123
     */
    public static void main(String[] args) {
//        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1, 2, 3, 4, 5});
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));
        }
        System.out.println();
        int tempInt = 0;
        tempInt = atomicIntegerArray.getAndSet(0, 1122);
        System.out.println(tempInt + "\t" + atomicIntegerArray.get(0));
        tempInt = atomicIntegerArray.getAndIncrement(0);
        System.out.println(tempInt + "\t" + atomicIntegerArray.get(0));
    }
    
}

8.3 引用类型原子类

  • AtomicReference:引用类型原子类

  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题

    • 解决修改过几次
  • AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来

    • 解决是否修改过,它的定义就是将标记戳简化为 true/false ,类似于一次性筷子
public class AtomicMarkableReferenceDemo {

    private static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference(100, false);

    /**
     * CAS --- unsafe --- do while + ABA --- atomicStampedReference,atomicMarkableReference
     *
     * atomicStampedReference:version + 1
     *
     * atomicMarkableReference:一次,false,true
     *
     * 运行结果:
     * t1	默认标识: false
     * t2	默认标识: false
     * t2	t2线程 CAS Result false
     * t2	true
     * t2	1000
     */
    public static void main(String[] args) {
        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 1000, marked, !marked);

        }, "t1").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "\t" + "t2线程 CAS Result " + b);
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());
        }, "t2").start();
    }

}

8.4 对象的属性修改原子类

  • AtomicIntegerFieldUpdater:原子更新对象中 int 类型字段的值

  • AtomicLongFieldUpdater:原子更新对象中 long 类型字段的值

  • AtomicReferenceFieldUpdater:原子更新对象中引用类型字段的值

8.4.1 使用目的

以一种线程安全的方式操作非线程安全对象内的某些字段

image

8.4.2 使用要求

  • 更新的对象属性必须使用 public volatile 修饰符

  • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性

8.4.3 Case

面试官问你:你在哪里使用了 volatile?

AtomicReferenceFiledUpdater

AtomicIntegerFieldUpdater 案例演示

public class AtomicIntegerFieldUpdaterDemo {

    /**
     * 以一种线程安全的方式操作非线程安全对象的某些字段
     *
     * 需求:
     * 10 个线程,
     * 每个线程转账 1000,
     * 不使用 synchronized,尝试使用 AtomicIntegerFieldUpdater 来实现
     */
    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000; j++) {
//                        bankAccount.add();
                        bankAccount.transMoney(bankAccount);
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + bankAccount.money);
    }

}

class BankAccount {

    private String bankName = "CCB";
    //更新对象的属性必须使用 public volatile 修饰
    public volatile int money = 0;

    public synchronized void add() {
        money++;
    }

    /**
     * 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
     * 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性
     */
    private static final AtomicIntegerFieldUpdater<BankAccount> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");

    /**
     * 不加 synchronized,保证高性能原子性,局部微创小手术
     */
    public void transMoney(BankAccount bankAccount) {
        fieldUpdater.getAndIncrement(bankAccount);
    }

}

AtomicReferenceFieldUpdater 案例演示

public class AtomicReferenceFieldUpdaterDemo {

    /**
     * 需求:
     * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作
     * 要求只能被初始化一次,只有一个线程操作成功
     *
     * 运行结果:
     * 0	---- start init,need 3 seconds
     * 3	---- 已经有线程在进行初始化工作....
     * 2	---- 已经有线程在进行初始化工作....
     * 1	---- 已经有线程在进行初始化工作....
     * 4	---- 已经有线程在进行初始化工作....
     * 0	---- over init
     */
    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            }, String.valueOf(i)).start();
        }
    }

}

class MyVar {

    public  volatile  Boolean isInit = Boolean.FALSE;
    static final AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "---- start init,need 3 seconds");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "---- over init");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "---- 已经有线程在进行初始化工作....");
        }
    }

}

8.5 原子操作增强类原理深度解析

  • DoubleAccumulator:一个或多个变量,它们一起保持运行 double 使用所提供的功能更新值

  • DoubleAdder:一个或多个变量一起保持初始值为零 double 总和

  • LongAccumulator:一个或多个变量,一起保持使用提供的功能更新运行的值 long,提供了自定义的函数操作

  • LongAdder:一个或多个变量一起维持初始为零 long 总和(重点),只能用来计算加法,且从 0 开始计算

8.5.1 常用 API 简介

void add(long x)	//将当前的 value 加 x
void increment()	//将当前的 value 加 1
void decrement()	//将当前的 value 减 1
long sum()	//返回当前值。特别注意,在没有并发更新 value 的情况下,sum 会返回一个精确值,在存在并发的情况下,sum 不保证返回精确值
void reset()	//将 value 重置为 0,可用于替代重新 new 一个 LongAdder,但此方法只可以在没有并发更新的情况下使用
long sumThenReset()	//获取当前 value,并将 value 重置为 0

8.5.2 Case

public class LongAdderAPIDemo {

    /**
     * 运行结果:
     * 3
     * 4
     */
    public static void main(String[] args) {
        LongAdder longAdder = new LongAdder();
        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        System.out.println(longAdder.sum());

        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
        longAccumulator.accumulate(1);
        longAccumulator.accumulate(3);
        System.out.println(longAccumulator.get());
    }

}

8.5.3 面试题

volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题

说明:如果是 count++ 操作,使用如下类实现:

AtomicInteger count = new AtomicInteger();
count.addAndGet(1);

如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)

  1. 热点商品点赞计算器,点赞数加加统计,不要求实时精确

  2. 一个很大的 list,里面都是 int 类型,如何实现加加,思路?

8.5.4 点赞计数器

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Supplier;

public class AccumulatorCompareDemo {

    public static final int _1W = 10000;
    public static final int threadNumber = 50;

    /**
     * 需求:50 个线程,每个线程 100w 次,总点赞数出来
     *
     * 运行结果:
     * ------costTime: 1095 毫秒	clickBySynchronized:50000000
     * ------costTime: 627 毫秒	clickByAtomicInteger:50000000
     * ------costTime: 52 毫秒	clickByLongAdder:50000000
     * ------costTime: 29 毫秒	clickByLongAccumulator:50000000
     */
    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        countNumber("clickBySynchronized" , clickNumber::clickBySynchronized, () -> clickNumber.number);
        countNumber("clickByAtomicInteger" , clickNumber::clickByAtomicInteger, () -> clickNumber.atomicInteger.get());
        countNumber("clickByLongAdder" , clickNumber::clickByLongAdder, () -> (int) clickNumber.longAdder.sum());
        countNumber("clickByLongAccumulator" , clickNumber::clickByLongAccumulator, () -> (int) clickNumber.longAccumulator.get());
    }

    private static void countNumber(String type, Runnable runnable, Supplier<Integer> resultSupplier) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(threadNumber);
        Long startTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < _1W * 100; j++) {
                        runnable.run();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        Long endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - startTime) + " 毫秒" + "\t" + type + ":" + resultSupplier.get());

    }

}

class ClickNumber {

    int number = 0;

    public synchronized void clickBySynchronized() {
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();

    public void clickByAtomicInteger() {
        atomicInteger.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();

    public void clickByLongAdder() {
        longAdder.increment();
    }

    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void clickByLongAccumulator() {
        longAccumulator.accumulate(1);
    }

}

8.5.5 源码、原理分析

架构

image

LongAdder 是 Striped64 的子类

Striped64 重要的成员函数

/** 
 *	Number of CPUs, to place bound on table size
 *	CPU 数量,即 cells 数组的最大长度 
 */
static final int NcPU = Runtime.getRuntime().availableProcessors();

/** 
 *	Table of cells. hhen non-null, size is a power of 2.	
 *	cells 数组,为 2 的幂,2,4,8,16.....,方便以后位运算 
 */
transient volatile cell[] cells;

/** 
 *	基础 value 值,当并发较低时,只累计该值,主要用于没有竞争的情况,通过 CAS 更新
 *	Base value, used mainly when there is no contention, but also as
 *	a fallback during table initialization races, Updated via CAs.
 */
transient volatile long base;

/** 
 *	创建或扩容 cells 数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁
 *	Spinlock (locked via CAS) used when resizing and/or creating Cells.
 */
transient volatile int cellsBusy;

Striped64 中一些变量或者方法的定义:

  • base:类似于 AtomicLong 中全局的 value 值。在没有竞争情况下数据直接累加到 base 上,或者 cells 扩容时,也需要就将数据写入到 base 上

  • collide:表示扩容意向,false 一定不会扩容,true 可能会扩容

  • cellsBusy:初始化 cells 或者扩容 cells 需要获取锁,0:表示无锁状态,1:表示其他线程已经持有了锁

  • casCellsBusy():通过 CAS 操作修改 cellsBusy 的值,CAS 成功代表获取锁,返回 true

  • NCPU:当前计算机 CPU 数量,cell 数组扩容时会使用到

  • getProbe():获取当前线程的 hash 值

  • advanceProbe():充值当前线程的 hash 值

Cell:是 java.util.concurrent.atomic下 Striped64 的一个内部类

@sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

LongAdder 为什么这么快?

  • LongAdder 的基本思路就是分散热点,将 value 的值分散到一个 Cell 数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回

  • sum()会将所有 Cell 数组中的 value 和 base 累加作为返回值,核心思想就是将之前 AtomicLong 一个 value 的更新压力分散到多个 value 中去,从而降级更新热点

内部有一个 base 变量,一个 Cell[] 数组

base 变量:低并发,直接累加到该变量上

Cell[] 数组:高并发,累加进各个线程自己的槽 Cell[i] 中

image

源码解读深度分析

LongAdder 在无竞争的情况下,跟 AtomicLong 一样,对同一个 base 进行操作,当出现竞争关系时则采用化整为零分散热点的做法,用空间换时间,用一个数组 cells,将一个 value 值拆分进这个数组 cells。cells 数组扩容默认是 2 的倍数。多个线程需要同时对 value 进行操作的时候,可以对线程 id 进行 hash 得到 hash 值,再根据 hash 值映射到这个数组 cells 的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组 cells 的所有值和 base 都加起来作为最终结果

increment()方法

public void increment() {
    add(1L);
}
  1. 最初无竞争时只更新 base

  2. 如果更新 base 失败后,首次新建一个 Cell[] 数组

  3. 当多个线程竞争同一个 Cell 比较激烈时,可能就要对 Cell[] 扩容

public void add(long x) {
    // as 是 Striped64 中的 cells 数组属性
    // b 是 Striped64 中的 base 属性
    // v 是当前线程 hash 到的 Cell 中存储的值
    // m 是 cells 的长度减 1,hash 时作为掩码使用
    // a 是当前线程 hash 到的 Cell
    Cell[] as; long b, v; int m; Cell a;
    //首次线程 (as = cells) != null 一定是 false,此时走 casBase 方法,以 CAS 的方式更新 base 值,且只有当 cas 失败时,才会走到 if 中
    //条件1:cells 不为空
    //条件2:cas 操作 base 失败,说明其他线程先一步修改了 base 正在出现竞争
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // true:无竞争,false:表示竞争激烈,多个线程 hash 到同一个 Cell,可能要扩容
        boolean uncontended = true;
        //条件1:cells 为空
        //条件2:应该不会出现
        //条件3:当线程所在的 Cell 为空,说明当前线程还没有更新过 Cell,应初始化一个 Cell
        //条件4:更新当前线程所在的 Cell 失败,说明现在竞争很激烈,多个线程 hash 到了同一个 Cell,应扩容
        if (as == null || (m = as.length - 1) < 0 ||
            // getProbe() 方法返回的时线程中的 threadLocalRandomProbe 字段
            //它时通过随机数生成一个值,对于一个确定的线程这个值是固定的(除非刻意修改它)
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

具体逻辑如上,简单的如下

  1. 如果 Cells 表为空,尝试用 CAS 更新 base 字段,成功则退出

  2. 如果 Cells 表为空,CAS 更新 base 字段失败,出现竞争, uncontended 为 true,调用 longAccumulate(新建数组)

  3. 如果 Cells 表非空,但当前线程映射的槽为空, uncontended 为 true,调用 longAccumulate(初始化)

  4. 如果 Cells 表非空,且当前线程映射的槽非空,CAS 更新 Cell 的值,成功则返回,否则, uncontended 设为 false,调用 longAccumulate(扩容)

getProbe()方法

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

private static final long PROBE;
static {
    try {
        ...
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;

longAccumulate() 方法

/**
 * long x 需要增加的值,一般默认都是 1
 * LongBinaryOperator fn 默认传递的是 null
 * wasUncontended 竞争标识,如果是 false 则代表有竞争。只有 cells 初始化之后,并且当前线程 CAS 竞争修改失败,才会是 false
 */
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
    	//存储线程的 probe 值
        int h;
    	//如果 getProbe() 方法返回0,说明随机数未初始化
        if ((h = getProbe()) == 0) {
            //使用 ThreadLocalRandom 为当前线程重新计算一个 hash 值,强制初始化
            ThreadLocalRandom.current(); // force initialization
            //重新获取 probe 的值,hash 值被重置,就好比一个全新的线程一样,所以设置了 wasUncontended 竞争状态为 true
            h = getProbe();
            //重新计算了当前线程的 hash 后认为此次不算是一次竞争,都未初始化,肯定还不存在竞争激烈 wasUncontended 竞争状态为 true
            wasUncontended = true;
        }
    	//如果 hash 取模映射得到的 Cell 单元不是 null,则为 true,此值也可以看作是扩容意向
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            // CASE1:cells 已经被初始化了
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            // CASE2:cells 没有加锁且没有初始化,则尝试对它进行加锁,并初始化 cells 数组
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // CASE3:cells 正在进行初始化,则尝试直接在基数 base 上进行累加操作
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

image

sum() 方法

public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}
  • sum()会将所有 Cell 数组中的 value 和 base 累加作为返回值。核心思想就是将之前 AtomicLong 一个 value 的更新压力分散到多个 value 中去,从而降级更新热点

  • sum()执行时,并没有限制对 base 和 cells 的更新,所以 LongAdder 不是强一致性的,它是最终一致性的,对 cell 的读取无法保证是最后一次写入的值,所以在没有并发的场景下,可以获得正确的结果

使用总结:

  • AtomicLong 线程安全,可允许一些性能损耗,要求高精度时可使用,保证精度,多个线程对单个热点值 value 进行了原子操作----保证精度,性能代码

  • LongAdder 在高并发场景下有较好的性能表现,且对值得精度要求不高时,可以使用,LongAdder 时每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行 CAS 操作----保证性能,精度代价

8.5.6 总结

AtomicLong

  • 原理:CAS + 自旋

  • 场景:低并发的全局计算,AtomicLong 能保证并发情况下计数的准确性,其内部通过 CAS 来解决并发安全性问题

  • 缺陷:高并发后性能急剧下降----AtomicLong 的自旋会成为瓶颈(N 个线程 CAS 操作修改线程的值,每次只有一个线程成功过,其他 N-1 失败,失败的不停自旋直至成功,这样大量失败自旋的情况,一下子 CPU 就打高了)

LongAdder

  • 原理:CAS + Base + Cell 数组分散----空间换时间并分散了热点数据

  • 场景:高并发下的全局计算

  • 缺陷:sum 求和后还有计算线程修改结果的话,最后结果不够准确

posted @ 2026-04-16 20:05  清风含薰  阅读(2)  评论(0)    收藏  举报