首页 > 其他分享 >原子操作增强类LongAdder

原子操作增强类LongAdder

时间:2022-12-25 23:11:53浏览次数:41  
标签:增强 LongAdder 原子 Cell base 线程 AtomicLong new

一. 性能对比
阿里开发手册推荐jdk8使用LongAdder替代AtomicLong

 

 

 

 

示例代码
题目:热点商品点赞计算器,点赞数加加统计,不要求实时精确。50个线程,每个线程100W次,统计总点赞数

比较synchronized、AtomicInteger、AtomicLong、LongAdder、LongAccumulator五种计数性能

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();
    }
 
    LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,0);
    public void add_longAccumulator(){
        longAccumulator.accumulate(1);
    }
}
 
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 = System.currentTimeMillis();
        long endTime = System.currentTimeMillis();
        CountDownLatch latch_synchronized = new CountDownLatch(SIZE_THREAD);
        CountDownLatch latch_AtomicInteger= new CountDownLatch(SIZE_THREAD);
        CountDownLatch latch_AtomicLong = new CountDownLatch(SIZE_THREAD);
        CountDownLatch latch_LongAdder = new CountDownLatch(SIZE_THREAD);
        CountDownLatch latch_LongAccumulator = 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 {
                    latch_synchronized.countDown();
                }
            },String.valueOf(i)).start();
        }
        latch_synchronized.await();
        endTime = System.currentTimeMillis();
        System.out.println("synchronized花费时间:"+ (endTime-startTime)+" 数值为:"+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 {
                    latch_AtomicInteger.countDown();
                }
            },String.valueOf(i)).start();
        }
        latch_AtomicInteger.await();
        endTime = System.currentTimeMillis();
        System.out.println("AtomicInteger花费时间:"+ (endTime-startTime)+" 数值为:"+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 {
                    latch_AtomicLong.countDown();
                }
            },String.valueOf(i)).start();
        }
        latch_AtomicLong.await();
        endTime = System.currentTimeMillis();
        System.out.println("AtomicLong花费时间:"+ (endTime-startTime)+" 数值为:"+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 {
                    latch_LongAdder.countDown();
                }
            },String.valueOf(i)).start();
        }
        latch_LongAdder.await();
        endTime = System.currentTimeMillis();
        System.out.println("LongAdder花费时间:"+ (endTime-startTime)+" 数值为:"+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 {
                    latch_LongAccumulator.countDown();
                }
 
            },String.valueOf(i)).start();
        }
        latch_LongAccumulator.await();
        endTime = System.currentTimeMillis();
        System.out.println("LongAccumulator花费时间:"+ (endTime-startTime)+" 数值为:"+clickNumber.longAccumulator.longValue());
 
    }
}

 

 

 


通过结果,可知LongAdder性能最优,花费时间最短,远优于AtomicLong

二. LongAdder为何那么快

LongAdder的基本思路

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

LongAdder的原理


LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作(无并发,单线程下直接CAS操作更新base值;非竞态条件下,直接累加到变量base上)

当出现竞争关系时则是采用化整为零的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组Cells.多个线程需要同时对value进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。(有并发,多线程下分段CAS操作更新Cell数组值;竞态条件下,累加个各个线程自己的槽Cell[]中)

 

 

 

 

sum()公式

 

 

 

三. 源码解析
1. LongAdder.add()

 

 

 

Cell[] as; long b, v; int m; Cell a;

as是striped64中的cells数组属性
b是striped64 中的base属性 v是当前线程hash到的Cell中存储的值
m是cells的长度减1,hash时作为掩码使用
a是当前线程hash到的Cell

if ((as = cells) != null || !casBase(b = base, b + x))

条件1: cells不为空,说明出现过竞争,Ccell[]已创建
条件2: cas操作base失败,说明其它线程先一步修改 了base正在出现竞争

首次首线程((as = cells) != null)一定是false,此时走casBase方法,以CAS的方式更新base值,且只有当cas失败时,才会走到if中

boolean uncontended = true

true无竞争,false表示竞争激烈,多个线程hash到同一个cell,可能要扩容

if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x))

条件1: cells为空,说明正在出现竞争,上面是从条件2过来的
条件2: 应该不会出现
条件3: 当前线程所在的cell为空,说明当前线程还没有更新过cell,应初始化一个cell
条件4: 更新当前线程所在的cell失败,说明现在竞争很激烈,多个线程hash到了同一个个Cell,应扩容

longAccumulate(x, null, uncontended)

调用striped64中的方法处理

小结

1.最初无竞争时只更新base;
2.如果更新base失败后,首次新建一个Cell[]数组
3.当多个线程竞争同一个CelI比较激烈时,可能就要对Cell[]扩容

2. Striped64.longAccumulate()
longAccumulate()方法的入参

long X需要增加的值,一般默认都是1
LongBinaryOperator fn默认传递的是null
wasUncontended竞争标识,如果是false则代表有竞争。只有cells初始化之后, 并且当前
失败,才会是false

 

 

 

 

代码解释

上述代码首先给当前线程分配一个hash值,然后进入一个for(;;)自旋,这个自旋分为三个分支:
CASE1: Cell[]数组已经初始化

  


CASE2: Cell[]数组未初始化(首次新建)

 

 

 


如果上面条件都执行成功就会执行数组的初始化及赋值操作

Cell[] rs = new Cell[2]表示数组的长度为2;rs[h & 1] = new Cell(x) 表示创建一个新的Cell元素,value是x值,默认为1;h & 1类似于HashMap常用到的计算散列桶index的算法,通常都是hash & (table.len - 1),同hashmap一个意思

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

 

 

 

 

 

 

 

多个线程尝试CAS修改失败的线程会走到这个分支,该分支实现直接操作base基数,将值累加到base上,也即其它线程正在初始化,多个线程正在更新base的值。

3. LongAdder.sum()

 

 

sum执行时,并没有限制对base和cells的更新。所以LongAdder不是强一致性的,它是最终一致性的(也就是说并发情况下,sum的值并不精确)

首先,最终返回的sum局部变量,初始被复制为base,而最终返回时,很可能base已经被更新了,而此时局部变量sum不会更新,造成不一致。其次,这里对cell的读取也无法保证是最后一次写入的值。所以,sum方法在没有并发的情况下,可以获得正确的结果

四. 与AtomicLong对比
AtomicLong
原理

CAS + 自旋

场景

低并发下的全局计算,AlomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。可允许一些性能损耗,要求高精度时可使用。AtomicLong是多个线程针对单个热点值value进行原子操作

缺陷

高并发后性能急剧下降。(N个线程CAS操作修改线程的值,每次只有一个成功过,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,占用大量CPU)

LongAdder
原理

CAS+Base+Cell数组分散,通过空间换时间分散了热点数据

场景

高并发下的全局计算,当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

缺陷

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

原文链接:https://blog.csdn.net/weixin_43899792/article/details/124575032

标签:增强,LongAdder,原子,Cell,base,线程,AtomicLong,new
From: https://www.cnblogs.com/shoshana-kong/p/17004829.html

相关文章

  • LongAdder详解以及底层原理分析
    一、原子累加器我们都知道,原子整型可以在线程安全的前提下做到累加功能,而今天介绍的LongAdder具有更好的性能我们先来看原子累加器和原子整型做累加的对比使用:priva......
  • LongAdder类实现原理、源码解析
    1.概述AtomicLong通过循环CAS实现原子操作,缺点是当高并发下竞争比较激烈的时候,会出现大量的CAS失败,导致循环CAS次数大大增加,这种自旋是要消耗时间cpu时间片的,......
  • 基于OpenVINO的端到端DL网络-A Year in Computer Vision中关于图像增强系列部分
    ​​http://www.themtank.org/a-year-in-computer-vision​​    TheMTank编辑了一份报告《AYearinComputerVision》,记录了2016至2017年计算......
  • PDF Tools - PDF增强工具
    背景PDF是PortableDocumentFormat的简称,意为“可携带文档格式”,是由AdobeSystems用于与应用程序、操作系统、硬件无关的方式进行文件交换所发展出的文件格式。PDF文件以PostScr......
  • 【图像增强】基于粒子群优化和模拟退火的图像增强算法研究Matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 深度学习炼丹-数据预处理和增强
    前言一,Normalization概述1.1,Normalization定义1.2,什么情况需要Normalization1.3,DataNormalization方法1.4,示例代码二,normalizeimages2.1,图像normalizati......
  • 气泡的图像增强
    气泡的图像增强对于这样的图片,如果只是基于普通的阈值处理、或者是梯度增强,都会因为背景比较复杂,从而结果不是很理想。很久之前,我的考虑就是要基于图像的本质特征。......
  • 【HuggingFace轻松上手】基于Wikipedia的知识增强预训练
    【HuggingFace轻松上手】基于Wikipedia的知识增强预训练前记:预训练语言模型(Pre-trainedLanguageModel,PLM)想必大家应该并不陌生,其旨在使用自监督学习(Self-supervisedLear......
  • MySQL8.0新特性-原子DDL
    MySQL8.0以前的DDLDDL(DataDefinitionLanguage)定义了数据在数据库中的结构、关系以及权限等,比如CREATE、ALTER、DROP、GRANT等等。在MySQL8.0之前的版本中,由于架构的原......
  • 正点原子STM32-串口协议学习笔记
    bit15bit14bit13~0接收完成标志接收到0x0d接收到的有效字节数过程:接收abcd然后/n最后立结束标志位为1对数组USART2_RX_BUF[]处理时,发现数组不......