Java多线程:LongAdder 原子操作增强类
Posted 杨 戬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程:LongAdder 原子操作增强类相关的知识,希望对你有一定的参考价值。
文章目录
AtomicXXX带来的问题
高并发下计数,一般最先想到的应该是AtomicLong/AtomicInt,AtmoicXXX使用硬件级别的指令 CAS 来更新计数器的值,这样可以避免加锁,机器直接支持的指令,效率也很高。
但是AtomicXXX中的 CAS操作在出现线程竞争时,失败的线程会白白地循环一次,在并发很大的情况下,因为每次CAS都只有一个线程能成功,竞争失败的线程会非常多。失败次数越多,循环次数就越多,很多线程的CAS操作越来越接近 自旋锁(spin lock)。
计数操作本来是一个很简单的操作,实际需要耗费的cpu时间应该是越少越好,AtomicXXX在高并发计数时,大量的cpu时间都浪费会在 自旋 上了,这很浪费,也降低了实际的计数效率。
LongAdder
LongAdder 概述
LongAdder是jdk8新增的用于并发环境的计数器,目的是为了在高并发情况下,代替AtomicLong/AtomicInt,成为一个用于高并发情况下的高效的通用计数器。说LongAdder比在高并发时比AtomicLong更高效
阿里开发手册推荐jdk8使用LongAdder替代AtomicLong
LongAdder 实现原理
LongAdder是根据锁分段来实现的,它里面维护一组按需分配的计数单元,并发计数时,不同的线程可以在不同的计数单元上进行计数,这样减少了线程竞争,提高了并发效率。本质上是用空间换时间的思想,不过在实际高并发情况中消耗的空间可以忽略不计。
LongAdder 具体实现
具体实现LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
cells数组操作示例图如下:
LongAdder在无竞争的情况,跟AtomicLong一样,对同一个base进行操作
当出现竞争关系时则是采用化整为零的做法,从空间换时间,用一个数组cells,将一个value拆分进这个数组Cells。多个线程需要同时对value进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。
当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果。
总结:
如果无并发,单线程下直接CAS操作更新base值;非竞态条件下,直接累加到变量base上
如果有并发,多线程下分段CAS操作更新Cell数组值;竞态条件下,累加各个线程到自己的槽Cell[]中
总结
现在,在处理高并发计数时,应该优先使用LongAdder,而不是继续使用AtomicLong。当然,线程竞争很低的情况下进行计数,使用Atomic还是更简单更直接,并且效率稍微高一些。
其他情况,比如序号生成,这种情况下需要准确的数值,全局唯一的 AtomicLong 才是正确的选择,此时不应该使用 LongAdder。
以上是关于Java多线程:LongAdder 原子操作增强类的主要内容,如果未能解决你的问题,请参考以下文章
Juc15_基本AtomicInteger数组引用AtomicStampedReference对象的属性修改原子类AtomicIntegerFieldUp 原子操作增强类LongAdder
Juc15_基本AtomicInteger数组引用AtomicStampedReference对象的属性修改原子类AtomicIntegerFieldUp 原子操作增强类LongAdder