java 中的LongAdder

悠悠 2023-07-16 10:54 28阅读 0赞

问题

(1)java8中为什么要新增LongAdder?

(2)LongAdder的实现方式?

(3)LongAdder与AtomicLong的对比?

简介

LongAdder是java8中新增的原子类,在多线程环境中,它比AtomicLong性能要高出不少,特别是写多的场景。

原理

LongAdder的原理是,在最初无竞争时,只更新base的值,当有多线程竞争时通过分段的思想,让不同的线程更新不同的段,最后把这些段相加就得到了完整的LongAdder存储的值。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1b3Fpbmdsb25nODUwMTAy_size_16_color_FFFFFF_t_70

源码分析

LongAdder继承自Striped64抽象类,Striped64中定义了Cell内部类和各重要属性。

主要内部类

  1. // Striped64中的内部类,使用@sun.misc.Contended注解,说明里面的值消除伪共享
  2. @sun.misc.Contended static final class Cell {
  3. // 存储元素的值,使用volatile修饰保证可见性
  4. volatile long value;
  5. Cell(long x) { value = x; }
  6. // CAS更新value的值
  7. final boolean cas(long cmp, long val) {
  8. return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
  9. }
  10. // Unsafe实例
  11. private static final sun.misc.Unsafe UNSAFE;
  12. // value字段的偏移量
  13. private static final long valueOffset;
  14. static {
  15. try {
  16. UNSAFE = sun.misc.Unsafe.getUnsafe();
  17. Class<?> ak = Cell.class;
  18. valueOffset = UNSAFE.objectFieldOffset
  19. (ak.getDeclaredField("value"));
  20. } catch (Exception e) {
  21. throw new Error(e);
  22. }
  23. }
  24. }

Cell类使用@sun.misc.Contended注解,说明是要避免伪共享的。

使用Unsafe的CAS更新value的值,其中value的值使用volatile修饰,保证可见性。

主要属性

  1. // 这三个属性都在Striped64中
  2. // cells数组,存储各个段的值
  3. transient volatile Cell[] cells;
  4. // 最初无竞争时使用的,也算一个特殊的段
  5. transient volatile long base;
  6. // 标记当前是否有线程在创建或扩容cells,或者在创建Cell
  7. // 通过CAS更新该值,相当于是一个锁
  8. transient volatile int cellsBusy;

LongAdder VS AtomicLong 性能对比

  1. public class LongAdderVSAtomicLongTest {
  2. public static void main(String[] args){
  3. testAtomicLongVSLongAdder(1, 10000000);
  4. testAtomicLongVSLongAdder(10, 10000000);
  5. testAtomicLongVSLongAdder(20, 10000000);
  6. testAtomicLongVSLongAdder(40, 10000000);
  7. testAtomicLongVSLongAdder(80, 10000000);
  8. }
  9. static void testAtomicLongVSLongAdder(final int threadCount, final int times){
  10. try {
  11. System.out.println("threadCount:" + threadCount + ", times:" + times);
  12. long start = System.currentTimeMillis();
  13. testLongAdder(threadCount, times);
  14. System.out.println("LongAdder elapse:" + (System.currentTimeMillis() - start) + "ms");
  15. long start2 = System.currentTimeMillis();
  16. testAtomicLong(threadCount, times);
  17. System.out.println("AtomicLong elapse:" + (System.currentTimeMillis() - start2) + "ms");
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. static void testAtomicLong(final int threadCount, final int times) throws InterruptedException {
  23. AtomicLong atomicLong = new AtomicLong();
  24. List<Thread> list = new ArrayList<>();
  25. for (int i=0;i<threadCount;i++){
  26. list.add(new Thread(() -> {
  27. for (int j = 0; j<times; j++){
  28. atomicLong.incrementAndGet();
  29. }
  30. }));
  31. }
  32. for (Thread thread : list){
  33. thread.start();
  34. }
  35. for (Thread thread : list){
  36. thread.join();
  37. }
  38. }
  39. static void testLongAdder(final int threadCount, final int times) throws InterruptedException {
  40. LongAdder longAdder = new LongAdder();
  41. List<Thread> list = new ArrayList<>();
  42. for (int i=0;i<threadCount;i++){
  43. list.add(new Thread(() -> {
  44. for (int j = 0; j<times; j++){
  45. longAdder.add(1);
  46. }
  47. }));
  48. }
  49. for (Thread thread : list){
  50. thread.start();
  51. }
  52. for (Thread thread : list){
  53. thread.join();
  54. }
  55. }
  56. }

可以看到当只有一个线程的时候,AtomicLong反而性能更高,随着线程越来越多,AtomicLong的性能急剧下降,而LongAdder的性能影响很小。

总结

(1)LongAdder通过base和cells数组来存储值;

(2)不同的线程会hash到不同的cell上去更新,减少了竞争;

(3)LongAdder的性能非常高,最终会达到一种无竞争的状态;

发表评论

表情:
评论列表 (有 0 条评论,28人围观)

还没有评论,来说两句吧...

相关阅读

    相关 LongAdder原理

         在阿里巴巴编码规范中,在高并发情况下,LongAdder的效率比AtomicLong要高,因为减少了AtomicLong空自旋的效率损耗,通过采用热点分离的思想来减少

    相关 JUC-LongAdder

    LongAdder只能用来计算加法,且从零开始计算 LongAccumulator提供了自定义的操作函数 ![watermark_type_ZHJvaWRzYW5zZmFs