2.LongAdder高性能对比Code演示
class ClickNumber //资源类 { int number = 0; public synchronized void clickBySynchronized() { number++; } AtomicLong atomicLong = new AtomicLong(0); public void clickByAtomicLong() { atomicLong.getAndIncrement(); } LongAdder longAdder = new LongAdder(); public void clickByLongAdder() { longAdder.increment(); } LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0); public void clickByLongAccumulator() { longAccumulator.accumulate(1); } } /** * 需求: 50个线程,每个线程100W次,总点赞数出来 */ public class AccumulatorCompareDemo { public static final int _1W = 10000; public static final int threadNumber = 50; public static void main(String[] args) throws InterruptedException { ClickNumber clickNumber = new ClickNumber(); long startTime; long endTime; CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber); CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber); CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber); CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber); startTime = System.currentTimeMillis(); for (int i = 1; i <=threadNumber; i++) { new Thread(() -> { try { for (int j = 1; j <=100 * _1W; j++) { clickNumber.clickBySynchronized(); } } finally { countDownLatch1.countDown(); } },String.valueOf(i)).start(); } countDownLatch1.await(); endTime = System.currentTimeMillis(); System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySynchronized: "+clickNumber.number); startTime = System.currentTimeMillis(); for (int i = 1; i <=threadNumber; i++) { new Thread(() -> { try { for (int j = 1; j <=100 * _1W; j++) { clickNumber.clickByAtomicLong(); } } finally { countDownLatch2.countDown(); } },String.valueOf(i)).start(); } countDownLatch2.await(); endTime = System.currentTimeMillis(); System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong: "+clickNumber.atomicLong.get()); startTime = System.currentTimeMillis(); for (int i = 1; i <=threadNumber; i++) { new Thread(() -> { try { for (int j = 1; j <=100 * _1W; j++) { clickNumber.clickByLongAdder(); } } finally { countDownLatch3.countDown(); } },String.valueOf(i)).start(); } countDownLatch3.await(); endTime = System.currentTimeMillis(); System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder: "+clickNumber.longAdder.sum()); startTime = System.currentTimeMillis(); for (int i = 1; i <=threadNumber; i++) { new Thread(() -> { try { for (int j = 1; j <=100 * _1W; j++) { clickNumber.clickByLongAccumulator(); } } finally { countDownLatch4.countDown(); } },String.valueOf(i)).start(); } countDownLatch4.await(); endTime = System.currentTimeMillis(); System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator: "+clickNumber.longAccumulator.get()); } }
3.源码、原理分析
LongAdder是Striped64的子类
两个重要类
Striped64 Number
原理(LongAdder为什么这么快)
LongAddre:当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong。在低更新争用下,这两个类具有相似的特征。但在高征用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
1.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;
最重要的两个
2.Striped64中一些变量或者方法的定义
3.cell
是 java.util.concurent.atomic 下 Striped64 的一个内部类
4.LongAdder为什么这么快
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回(Base值+Cell数组全部求和)。
sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去, 从而降级更新热点。
总结:
内部有一个base变量,一个Cell[] 数组。
base变量:非竞态条件下,直接累加到该变量上
Cell[] 数组:竞态条件下,累加个各个线程自己的槽Cell[i] 中
6.源码解读深度分析
LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系时则是采用化整为零分散热点的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。
longAdder.increment()
使用总结
AtomicLong
线程安全,可允许一些性能损耗,要求高精度时可使用
保证精度,性能代价
AtomicLong是多个线程针对单个热点值value进行原子操作
LongAdder
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
保证性能,精度代价
LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
7、总结
AtomicLong
原理
CAS+自旋
incrementAndGet
场景
低并发下的全局计算
AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
缺陷
高并发后性能急剧下降
AtomicLong的自旋会成为瓶颈
N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
LongAdder
原理
CAS+Base+Cell数组分散
空间换时间并分散了热点数据
场景
高并发下的全局计算
缺陷
sum求和后还有计算线程修改结果的话,最后结果不够准确