首页 > 编程语言 >Java并发工具类之LongAdder原理总结

Java并发工具类之LongAdder原理总结

时间:2024-04-23 18:23:36浏览次数:18  
标签:LongAdder Java long Cell 并发 线程 value final

出处: Java并发工具类之LongAdder原理总结

LongAdder实现原理图

                               

                               

  高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源。

   LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。

  LongAdder维护了要给延迟初始化的原子性更新数组和一个基值变量base数组的大小保持是2的N次方大小,数组表的下标使用每个线程的hashcode值的掩码表示,数组里面的变量实体是Cell类型。

  Cell 类型是Atomic的一个改进,用来减少缓存的争用,对于大多数原子操作字节填充是浪费的,因为原子操作都是无规律的分散在内存中进行的,多个原子性操作彼此之间是没有接触的,但是原子性数组元素彼此相邻存放将能经常共享缓存行,也就是伪共享。所以这在性能上是一个提升。

  另外由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候再创建,也就是惰性加载,当一开始没有空间时候,所有的更新都是操作base变量。

 

 

  java.util.concurrency.atomic.LongAdder是Java8新增的一个类,提供了原子累计值的方法。根据文档的描述其性能要优于AtomicLong,下图是一个简单的测试对比(平台:MBP):                 

                           image 

  这里测试时基于JDK1.8进行的,AtomicLong 从Java8开始针对x86平台进行了优化,使用XADD替换了CAS操作,我们知道JUC下面提供的原子类都是基于Unsafe类实现的,并由Unsafe来提供CAS的能力。CAS (compare-and-swap)本质上是由现代CPU在硬件级实现的原子指令,允许进行无阻塞,多线程的数据操作同时兼顾了安全性以及效率。大部分情况下,CAS都能够提供不错的性能,但是在高竞争的情况下开销可能会成倍增长,具体的研究可以参考这篇文章, 我们直接看下代码:

复制代码
public class AtomicLong {
public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }
}

public final class Unsafe {
public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
    }
}
复制代码

  getAndAddLong方法会以volatile的语义去读需要自增的域的最新值,然后通过CAS去尝试更新,正常情况下会直接成功后返回,但是在高并发下可能会同时有很多线程同时尝试这个过程,也就是说线程A读到的最新值可能实际已经过期了,因此需要在while循环中不断的重试,造成很多不必要的开销,而xadd的相对来说会更高效一点,伪码如下,最重要的是下面这段代码是原子的,也就是说其他线程不能打断它的执行或者看到中间值,这条指令是在硬件级直接支持的:

function FetchAndAdd(address location, int inc) {
    int value := *location
    *location := value + inc
    return value
}

  而LongAdder的性能比上面那种还要好很多,于是就研究了一下。首先它有一个基础的值base,在发生竞争的情况下,会有一个Cell数组用于将不同线程的操作离散到不同的节点上去(会根据需要扩容,最大为CPU核数),sum()会将所有Cell数组中的value和base累加作为返回值。核心的思想就是将AtomicLong一个value的更新压力分散到多个value中去,从而降低更新热点。

                 

public class LongAdder extends Striped64 implements Serializable {
//...
}

  LongAdder继承自Striped64Striped64内部维护了一个懒加载的数组以及一个额外的base实例域,数组的大小是2的N次方,使用每个线程Thread内部的哈希值访问。

复制代码
abstract class Striped64 extends Number {
/** Number of CPUS, to place bound on table size */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * Table of cells. When non-null, size is a power of 2.
     */
    transient volatile Cell[] cells;
     
@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);
            }
        }
    }

}
复制代码

  数组的元素是Cell类,可以看到Cell类用Contended注解修饰,这里主要是解决false sharing(伪共享的问题),不过个人认为伪共享翻译的不是很好

标签:LongAdder,Java,long,Cell,并发,线程,value,final
From: https://www.cnblogs.com/personsiglewine/p/18153499

相关文章

  • MyBatis所有的jdbcType类型对应的javaType类型
    来源:https://www.jb51.net/program/287517rew.htmMyBatis处理MySQL字段类型date与datetime1)DATETIME显示格式:yyyy-MM-ddHH:mm:ss时间范围:['1000-01-0100:00:00'到'9999-12-3123:59:59']2)DATE显示格式:yyyy-MM-dd时间范围:['1000-01-01'到'9999-12-31'......
  • 并发编程(ReentrantReadWriteLock)
    ReentrantReadWriteLock是一个可重入读写锁,内部提供了读锁和写锁的单独实现。其中读锁用于只读操作,可被多个线程共享;写锁用于写操作,只能互斥访问ReentrantReadWriteLock尤其适合读多写少的应用场景读多写少:在一些业务场景中,大部分只是读数据,写数据很少,如果这种场景下依然使用......
  • cmd命令行运行Java文件产生乱码问题的解决方式
    1.第一种方式(改变记事本的编码集为ANSI)另存为ANSI 这样就OK啦2.第二种方式 改变cmd编码集 javac-encodingutf8 源文件名.java ......
  • 【JAVA】助力数字化营销:基于协同过滤算法实现个性化商品推荐
    ​​周松杰:碧桂园服务后台开发gao级工程师,拥有8年开发经验。1前言本文主要介绍基于协同过滤的推荐算法。在了解实践应用之前,我们先对推荐系统、Mahout学习框架以及算法进行简单介绍,然后再结合实践进行深入探讨。第一部分概述了推荐系统的产生背景、作用和工作原理,并介绍了Ma......
  • c# 通过消息队列处理高并发请求实列
    网站面对高并发的情况下,除了增加硬件,优化程序提高以响应速度外,还可以通过并行改串行的思路来解决。这种思想常见的实践方式就是数据库锁和消息队列的方式。这种方式的缺点是需要排队,响应速度慢,优点是节省成本。演示一下现象创建一个在售产品表CREATETABLE[dbo].[product]([......
  • 并发编程(ReentrantLock)
    ReentrantLock是独占锁,每次只能有一个线程能获取到锁(支持重入)。其他未获取锁的线程会放入的CLH队列中,等待当前线程唤醒;主要分为公平锁和非公平锁,由内部类FairSync和NoFairSync来实现。主要的区别在于非公平锁每次都会尝试竞争,竞争不到锁才会放入到CLH队列中NonfairSync类......
  • Java面试题:请谈谈Java中的volatile关键字?
    在Java中,volatile关键字是一种特殊的修饰符,用于确保多线程环境下的变量可见性和顺序性。当一个变量被声明为volatile时,它可以确保以下两点:内存可见性:当一个线程修改了一个volatile变量的值,其他线程会立即看到这个改变。这是因为volatile关键字会禁止CPU缓存和编译器优化,从而确......
  • JTCR-java.util 更多实用类-18
    BitSetBitSet类是用于存放二进制位值的布尔数组,数组大小按需增加。构造器为BitSet();//指定初始化数组大小BitSet(intsize);publicclassBitSetDemo{//bit1的值//{0,2,4,6,8,10,12,14}//bit2的值//{1,2,3,4,6,7,8,9,11,12,......
  • day20-并发编程(下)
    1.多进程开发进程是计算机中资源分配的最小单元;一个进程中可以有多个线程,同一个进程中的线程共享资源;进程与进程之间则是相互隔离。Python中通过多进程可以利用CPU的多核优势,计算密集型操作适用于多进程。1.1进程介绍importmultiprocessingdeftask(): passif__name......
  • JTCR-java.util 集合框架-17
    JDK9开始,java.util包作为java.base模块的一部分。概述集合框架的设计目标高性能。不同类型的集合使用方式相似,有很好的互操作性。容易扩展或适配集合。Iterator接口提供了访问集合中元素通用、标准化的方式。任意集合类都可以使用Iterator提供的方法访问元素。JDK......