多线程源码学习-LongAdder和LongAccumulator

682 阅读3分钟

LongAdder和LongAccumulator用于大数据统计,且不要求实时精确。

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

例子

class Demo{
    public volatile int i = 0;

    public AtomicInteger atomicInteger = new AtomicInteger();

    public LongAdder longAdder = new LongAdder();

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

    public synchronized void syc_add(){
        i++;
    }

    public void syc_in_add(){
        synchronized (this){
            i++;
        }
    }

    public void atomic (){
        atomicInteger.incrementAndGet();
    }

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

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

public class LongAdderDemo {
    public static final int SIZE_THREAD = 50;
    public static final int _1W = 10000;

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        long startTime;
        long endTime;

        CountDownLatch countDownLatch1 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch2 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch3 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch4 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch5 = new CountDownLatch(SIZE_THREAD);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        demo.syc_add();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch1.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t syc_add()"+"\t"+demo.i);

        demo.i=0;

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        demo.syc_in_add();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch2.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t syc_in_add()"+"\t"+demo.i);


        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        demo.atomic();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch3.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t atomic()"+"\t"+demo.atomicInteger.get());


        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        demo.add_LongAccumulator();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch4.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_LongAccumulator()"+"\t"+demo.longAccumulator.get());


        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        demo.add();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch5.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch5.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add()"+"\t"+demo.longAdder.sum());
    }
}

image.png

源码分析

多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,在根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作,当所有线程操作完成,将数组cells的所有值和base值都加起来。

image.png

Value = Base + Cell[1]+...+Cell[N] image.png

继承图

image.png

Striped64(重要的属性)

image.png

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

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

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

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

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

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

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

add方法

image.png

  • as表示cells引用

  • b表示获取的base值

  • v表示期待值

  • m表示cells数组的长度

  • a表示当前线程命中的cell单元格

  • (cs = cells) != null 判断cells是否为null

  • casBase(b = base, b + x) 基于base使用cas是否更新成功

  • (m = cs.length - 1) < 0 判断cells数量是否小于零

  • cs == null 判断cell数组是否为null

  • (c = cs[getProbe() & m]) == null 计算线程id的hash值判断落点cell是否为null

  • (uncontended = c.cas(v = c.value, v + x) 判断是否需要扩容

longAccumulate方法

image.png case1:Cell[]已经初始化

case2:Cell[]数组未初始化

case3:Cell[]数组正在初始化中

case2

image.png

case1

判断当前线程hash后指向的数据位置元素是否为空,如果为空则将Cell数据放入数组中,跳出循环,如果不空则继续循环。

image.png

image.png

case3

在Base上进行操作 image.png

sum方法

遍历Cell[]进行累加,实现最终一致性而不是强一致性。 image.png