LongAdder与AtomicLong

LongAdder通过将计数任务分配给多个Cell,利用空间换取时间的方式提高在高并发环境下的计数效率。每个线程根据其ID哈希后定位到一个Cell进行独立计数,最终将所有Cell值相加得到总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用自循(CLH)方式不断地cas到特定的值,从而达到更新数据的目的。然而在线程竞争激烈的情况下,自循往往浪费很多计算资源才能达成预期效果。

       面对自循的缺点,jdk1.8推出了LongAdder类,他的实现方式有点像concurrentHashMap一样,采用空间换时间的方式,提高在线程竞争激烈的情况下,提供计数的效率,接下来我们看看源码是怎么实现的。

       看源码前,我们先明白源码做了些什么,这样有目的的看,往往事半功倍。

       LongAdder内里面存在一个Cell数组,计数是将线程id进行hash算法后,得到一个小于等于当前计算机核数的值,根据该值定位到一个cell数组,然后操作该cell进行单独计数。最终求总计数值时候,把所有的cell数组值相加即可。这样将多个线程操作一个AtomicLong转变为多个线程操作多个cell的情况,自然竞争小了,效率高了。可是存储空间大了。


注意:cell类前的注解@sun.misc.Contended,该注解涉及到伪共享(false share)问题,不了解的可以百度一下,在此不做累述。


  1. static final int NCPU = Runtime.getRuntime().availableProcessors();  
  2. final void longAccumulate(long x, LongBinaryOperator fn,  
  3.                           boolean wasUncontended) {  
  4.     int h;  
  5.     //获取当前线程的probe值作为hash值。  
  6.     if ((h = getProbe()) == 0) {  
  7.         //如果probe值为0,强制初始化当前线程的probe值,这次初始化的probe值不会为0。  
  8.         ThreadLocalRandom.current();   
  9.         //再次获取probe值作为hash值。  
  10.         h = getProbe();  
  11.         //这次相当于再次计算了hash,所以设置未竞争标记为true。  
  12.         wasUncontended = true;  
  13.     }  
  14.     boolean collide = false;  
  15.     for (;;) {  
  16.         Cell[] as; Cell a; int n; long v;  
  17.         if ((as = cells) != null && (n = as.length) > 0) {  
  18.             //通过h从cell表中选定一个cell位置。  
  19.             if ((a = as[(n - 1) & h]) == null) {  
  20.                 //如果当前位置没有cell,尝试新建一个。  
  21.                 if (cellsBusy == 0) {  
  22.                     //创建一个Cell。         
  23.                     Cell r = new Cell(x);   
  24.                     //尝试或者cellsBusy锁。  
  25.                     if (cellsBusy == 0 && casCellsBusy()) {  
  26.                         boolean created = false;  
  27.                         try {                 
  28.                             Cell[] rs; int m, j;  
  29.                             //在获取锁的情况下再次检测一下。  
  30.                             if ((rs = cells) != null &&  
  31.                                 (m = rs.length) > 0 &&  
  32.                                 rs[j = (m - 1) & h] == null) {  
  33.                                 //设置新建的cell到指定位置。  
  34.                                 rs[j] = r;  
  35.                                 //创建标记设置为true。  
  36.                                 created = true;  
  37.                             }  
  38.                         } finally {  
  39.                             //释放cellsBusy锁。  
  40.                             cellsBusy = 0;  
  41.                         }  
  42.                         if (created)  
  43.                             //如果创建成功,直接跳出循环,退出方法。  
  44.                             break;  
  45.                         //说明上面指定的cell的位置上有cell了,继续尝试。  
  46.                         continue;    
  47.                     }  
  48.                 }  
  49.                 //走到这里说明获取cellsBusy锁失败。  
  50.                 collide = false;  
  51.             }  
  52.             //以下条件说明上面通过h选定的cell表的位置上有Cell,就是a。  
  53.             else if (!wasUncontended)       // CAS already known to fail  
  54.                 //如果之前的CAS失败,说明已经发生竞争,  
  55.                 //这里会设置未竞争标志位true,然后再次算一个probe值,然后重试。  
  56.                 wasUncontended = true;      // Continue after rehash  
  57.             //这里尝试将x值加到a的value上。  
  58.             else if (a.cas(v = a.value, ((fn == null) ? v + x :  
  59.                                          fn.applyAsLong(v, x))))  
  60.                 //如果尝试成功,跳出循环,方法退出。  
  61.                 break;  
  62.             else if (n >= NCPU || cells != as)  
  63.                 //如果cell表的size已经最大,或者cell表已经发生变化(as是一个过时的)。  
  64.                 collide = false;             
  65.             else if (!collide)  
  66.                 //设置冲突标志,表示发生了冲突,重试。  
  67.                 collide = true;  
  68.             //尝试获取cellsBusy锁。  
  69.             else if (cellsBusy == 0 && casCellsBusy()) {  
  70.                 try {  
  71.                     //检测as是否过时。  
  72.                     if (cells == as) {        
  73.                         //给cell表扩容。  
  74.                         Cell[] rs = new Cell[n << 1];  
  75.                         for (int i = 0; i < n; ++i)  
  76.                             rs[i] = as[i];  
  77.                         cells = rs;  
  78.                     }  
  79.                 } finally {  
  80.                     //释放cellsBusy锁。  
  81.                     cellsBusy = 0;  
  82.                 }  
  83.                 collide = false;  
  84.                 //扩容cell表后,再次重试。  
  85.                 continue;                    
  86.             }  
  87.             //算出下一个hash值。  
  88.             h = advanceProbe(h);  
  89.         }  
  90.         //如果cell表还未创建,先尝试获取cellsBusy锁。  
  91.         else if (cellsBusy == 0 && cells == as && casCellsBusy()) {  
  92.             boolean init = false;  
  93.             try {                            
  94.                 if (cells == as) {  
  95.                     //初始化cell表,初始容量为2。  
  96.                     Cell[] rs = new Cell[2];  
  97.                     rs[h & 1] = new Cell(x);  
  98.                     cells = rs;  
  99.                     init = true;  
  100.                 }  
  101.             } finally {  
  102.                 //释放cellsBusy锁。  
  103.                 cellsBusy = 0;  
  104.             }  
  105.             if (init)  
  106.                 //初始化cell表成功后,退出方法。  
  107.                 break;  
  108.         }  
  109.         //如果创建cell表由于竞争导致失败,尝试将x累加到base上。  
  110.         else if (casBase(v = base, ((fn == null) ? v + x :  
  111.                                     fn.applyAsLong(v, x))))  
  112.             break;                            
  113.     }  
  114. }  


方法流程细节:
       首先,方法内部首先会算一个hash值,用来确定cell数组的下标。hash值初始源于当前Thread中的threadLocalRandomProbe域,如果hash值初始后为0,会初始化一下当前线程的threadLocalRandomProbe值,然后再次赋给hash值。注意方法传入第三个参数wasUncontended表示调用方法之前是否未发生竞争,加入前面走了初始化threadLocalRandomProbe的过程,就会将wasUncontended设置为true。
       接下来,方法进入主循环。
       1.先判断Cell表是否创建。
       1.1.如果Cell表未创建,尝试获取cellsBusy锁。
       1.1.1.如果获取cellsBusy锁成功,会创建一个size为2的Cell表作为初始cell表,然后新建一个保存给定x的Cell实例,然后根据hash值设置到Cell表对应的位置上;
       1.1.2.如果获取cellsBusy锁失败,会尝试将x累加到base上,失败重试。
       1.2.如果Cell表已经创建,通过hash值算出一个Cell表中的位置,然后获取这个位置上的Cell,称为a。
       1.2.1.如果a为null,尝试获取cellsBusy锁。
       1.2.1.1.如果获取cellsBusy成功,创建一个新的Cell,然后赋值给a,方法退出。(过程中需要多次检测冲突)
       1.2.1.2.如果获取cellsBusy失败,会将collide设置为false(实际上是表示发生了冲突),然后重试。
       1.2.2.如果a不为null。
       1.2.2.1.如果wasUncontended为false,说明之前发生过CAS竞争失败,设置wasUncontended为true,重新计算hash值,重试;如果wasUncontended为true,继续尝试下面过程。
       1.2.2.2.尝试通过CAS方式将x累加到a的value上,如果尝试成功,方法退出;如果尝试失败,继续尝试下面过程。
       1.2.2.3.如果当前Cell表的大小以及达到最大值(当前处理器核数),或者Cell表发生了变化(竞争导致过时),那么会设置collide为false,重新计算hash值,然后重试;否则,继续尝试下面过程。
       1.2.2.4.如果collide为false,说明之前发生过冲突,将collide设置为true,重新计算hash值,然后重试;否则,继续尝试下面过程。
       1.2.2.5.尝试获取cellsBusy,如果成功,扩展Cell表,并将collide设置为false,然后重试;否则,重新计算hash值,然后重试;


最后求cell值时候,把各个cell的value值相加即可



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timy07

创作不易,每一分都是真爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值