原子操作增强类LongAddr解析

1. 简介

LongAddr属于原子操作增强类,在传统的多线程编程中,我们可能会使用 synchronized 关键字或者 ReentrantLock 来保证对共享变量的原子性操作。然而,这些方法在高度并发的场景下可能会引起性能瓶颈。原子类中的AtomicInteger可以解决这个问题,但同时又出现了LongAddr来提供了一种更高效的并发累加方案。

2. AtomicInteger的局限性
竞争热点

当多个线程同时竞争修改同一个 AtomicInteger实例时,会发生竞争热点。所有的线程都试图通过 CAS 操作来更新同一变量,这可能导致竞争激烈,降低性能。

3. AtomicInteger与LongAdder 的性能差异
需求:热点商品点赞计算器,点赞数加加统计,不要求实时精确;

代码:使用50个线程,每个线程100W次,总点赞数出来。

class ClickNumber
{
    int number = 0;
    public synchronized void add_Synchronized()
    {
        number++;
    }
 
    AtomicInteger atomicInteger = new AtomicInteger();
    public void add_AtomicInteger()
    {
        atomicInteger.incrementAndGet();
    }
 
    AtomicLong atomicLong = new AtomicLong();
    public void add_AtomicLong()
    {
        atomicLong.incrementAndGet();
    }
 
    LongAdder longAdder = new LongAdder();
    public void add_LongAdder()
    {
        longAdder.increment();
        //longAdder.sum();
    }
 
    LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);
    public void add_LongAccumulator()
    {
        longAccumulator.accumulate(1);
    }
 
}
 
 
/**
 *
 *  50个线程,每个线程100W次,总点赞数出来
 */
public class LongAdderCalcDemo
{
    public static final int SIZE_THREAD = 50;
    public static final int _1W = 10000;
 
    public static void main(String[] args) throws InterruptedException
    {
        ClickNumber clickNumber = new ClickNumber();
        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++) {
                        clickNumber.add_Synchronized();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch1.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_Synchronized"+"\t"+clickNumber.number);
 
 
        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        clickNumber.add_AtomicInteger();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch2.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicInteger"+"\t"+clickNumber.atomicInteger.get());
 
        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        clickNumber.add_AtomicLong();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    countDownLatch3.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicLong"+"\t"+clickNumber.atomicLong.get());
 
        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        clickNumber.add_LongAdder();
                    }
                }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_LongAdder"+"\t"+clickNumber.longAdder.longValue());
 
        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
            new Thread(() -> {
                try
                {
                    for (int j = 1; j <=100 * _1W; j++) {
                        clickNumber.add_LongAccumulator();
                    }
                }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_LongAccumulator"+"\t"+clickNumber.longAccumulator.longValue());
    }
}
----costTime: 895 毫秒     add_Synchronized    50000000
----costTime: 450 毫秒     add_AtomicInteger    50000000
----costTime: 445 毫秒     add_AtomicLong    50000000
----costTime: 41 毫秒     add_LongAdder    50000000
----costTime: 41 毫秒     add_LongAccumulator    50000000
 通过结果能看出在duoLongAddr的性能要比synchronized锁和AtomicInteger要好。

4.LongAdder 的结构
LongAddr架构
      

从上图可以看出LongAdder是Striped64的子类。

Striped64中重要的属性

/** Number of CPUS, to place bound on table size        CPU数量,即cells数组的最大长度 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
 
/**
 * Table of cells. When 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中一些变量或者方法的定义
1.base:类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上
2.collide:表示扩容意向,false一定不会扩容,true可能会扩容。
3.cellsBusy:初始化cells或者扩容cells需要获取锁,0:表示无锁状态1:表示其他线程已经持有了锁
4.casCellsBusy():通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
5.NCPU:当前计算机CPU数量,CelI数组扩容时会使用到. getProbe():获取当前线程的hash值
6.advanceProbe():重置当前线程的hash值

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

5. 分散热点的原理
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
 
sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。

Value = Base +

总结:内部有一个base变量,一个Cell[]数组。

base变量:非竞态条件下,直接累加到该变量上,Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中。

具体流程图


6. 在实际项目中的应用
热点商品点赞计算器,点赞数加加统计,不要求实时精确。
一个很大的List,里面都是int类型,实现加加。
7. 总结
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。

即当需要保证性能,不要求精度,可以使用。
————————————————
                        
原文链接:https://blog.youkuaiyun.com/m0_46561911/article/details/134772052

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值