①. LongAdder的引入、原理、能否代替AtomicLong
①. 我们知道,AtomicLong是利用底层的CAS操作来提供并发性的,比如addAndGet方法:
下面方法调用了Unsafe类的getAndAddLong方法,该方法是一个native方法,它的逻辑是采用自旋的方式不断更新目标值,直到更新成功。(也即乐观锁的实现模式)
在并发量比较低的情况下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发情况下,N个线程同时进行自旋操作,N-1个线程失败,导致CPU打满场景,此时AtomicLong的自旋会成为瓶颈
这就是LongAdder引入的初衷------解决高并发环境下AtomictLong的自旋瓶颈问题
public final long addAndGet(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta) + delta; }
②. LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则采用化整为零的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果(分散热点)
③. LongAdder能否替代AtomicLong?
从下面的图中可以看到,LongAdder的API和AtomicLong的API还是有比较大的差异,而且AtomicLong提供的功能更丰富,尤其是addAndGet、decrementAndGet、compareAndSet这些方法。addAndGet、decrementAndGet除了单纯的做自增自减外,还可以立即获取增减后的值,而LongAdder则需要做同步控制才能精确获取增减后的值。如果业务需求需要精确的控制计数,则使用AtomicLong比较合适;
低并发、一般的业务尝尽下AtomicLong(数据准确)是足够了,如果并发量很多,存在大量写多读少的情况,那LongAdder(数据最终一致性,不保证强一致性)可能更合适
②. Striped64
- ①. Striped64有几个比较重要的成员函数
//CPU数量,即Cells数组的最大长度 static final int NCPU = Runtime.getRuntime().availableProcessors(); //存放Cell的hash表,大小为2的幂 //这里的Cell是Striped64的内部类 transient volatile Cell[] cells; /* 1.在开始没有竞争的情况下,将累加值累加到base; 2.在cells初始化的过程中,cells处于不可用的状态,这时候也会尝试将通过cas操作值累加到base */ transient volatile long base; /* cellsBusy,它有两个值0或1,它的作用是当要修改cells数组时加锁, 防止多线程同时修改cells数组(也称cells表),0为无锁,1位加锁,加锁的状况有三种: (1). cells数组初始化的时候; (2). cells数组扩容的时候; (3).如果cells数组中某个元素为null,给这个位置创建新的Cell对象的时候; */ 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值
③. LongAdder是Striped64的子类、架构图
④. Cell:是java.util.concurrent.atomic下Striped64下的一个内部类