一、LongAdder入门
API文档地址:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html
构造方法:初始值为0
LongAdder()
方法:
add(long x) decrement() // 减1 increment() // 加1 toString()
二、LongAdder于AtomicLong性能比较
通过案例比较synchronized、AtomicInteger、AtomicLong、LongAdder、LongAccumulator五种计数性能
ClickNumber类
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAccumulator; import java.util.concurrent.atomic.LongAdder; public 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(); // 加1 } LongAccumulator longAccumulator = new LongAccumulator((x, y)->x+y,0); public void add_longAccumulator(){ longAccumulator.accumulate(1); } }
测试类
import java.util.concurrent.CountDownLatch; public class LongAdderCalcDemo { public static final int SIZE_THREAD = 50; // 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是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。 // 当计数器值到达0时,它表示所有的线程已经完成了任务,然后就可以恢复等待的线程继续执行了。 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()); } }
结果:
synchronized花费时间:2483 数值为:50000000 AtomicInteger花费时间:653 数值为:50000000 AtomicLong花费时间:761 数值为:50000000 LongAdder花费时间:96 数值为:50000000 LongAccumulator花费时间:102 数值为:50000000
通过结果,可知LongAdder性能最优,花费时间最短,远优于AtomicLong.
LongAdder为何那么快?
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作(无并发,单线程下直接CAS操作更新base值;非竞态条件下,直接累加到变量base上)
当出现竞争关系时则是采用化整为零的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组Cells.多个线程需要同时对value进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。(有并发,多线程下分段CAS操作更新Cell数组值;竞态条件下,累加个各个线程自己的槽Cell[]中)
与AtomicLong对比
1、AtomicLong
原理:CAS + 自旋,
场景:低并发下的全局计算,AlomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。可允许一些性能损耗,要求高精度时可使用。AtomicLong是多个线程针对单个热点值value进行原子操作
缺陷:高并发后性能急剧下降。(N个线程CAS操作修改线程的值,每次只有一个成功过,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,占用大量CPU)
2、LongAdder
原理:CAS+Base+Cell数组分散,通过空间换时间分散了热点数据
场景:高并发下的全局计算,当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
缺陷:sum求和后还有计算线程修改结果的话,最后结果不够准确