先认全家族
java.util.concurrent.atomic 包比想象的大,按用途分五类:基本类型——AtomicInteger / AtomicLong / AtomicBoolean,计数与状态位;引用类型——AtomicReference(对象引用整体替换)、AtomicStampedReference(带版本号治 ABA)、AtomicMarkableReference(布尔标记版);数组——AtomicIntegerArray 等,数组元素的原子读写;字段更新器——AtomicIntegerFieldUpdater 等,给已有对象的 volatile 字段补上 CAS 能力,不用为个别字段重建整个类;累加器——LongAdder / DoubleAdder / LongAccumulator,高并发统计专用,本篇主角。
AtomicLong 的痛点:一个热点吵翻全场
几十个线程并发调 AtomicLong.incrementAndGet 时,CAS 失败率飙升:每次自增都要和全体线程抢同一个内存地址,失败、重试、再失败——上一篇写的那个 do-while 循环,在高并发下变成了自旋内耗,吞吐量随线程数不升反降。热点在哪?所有线程挤在同一个 value 上。
LongAdder:打不过就分家
LongAdder 的思路一句话:把热点摊开。它内部维护一个 base 加一个 Cell 数组:无竞争时只对 base 做 CAS,和 AtomicLong 一样快;一旦发现竞争(base CAS 失败),失败的线程被引导到各自的 Cell 槽位上自增——各改各的槽位,几乎零冲突;Cell 数组还会按竞争程度自动扩容。取值时 sum() 把 base 和所有 Cell 求和:
LongAdder adder = new LongAdder();
adder.increment(); // 各线程自增,内部自动分摊到 Cell
adder.sum(); // 需要时汇总:base + 所有 Cell 之和代价也直说:sum() 不是瞬时精确值——汇总时别的线程可能还在改,拿到的是弱一致的近似值。于是选型一句话:要精确的原子计数语义(生成序号、精确配额扣减)用 AtomicLong;要高并发统计(QPS、请求数、调用次数)用 LongAdder。统计场景在乎的是量级正确,不是某一瞬间的精确快照。
LongAccumulator:自定义累积运算
LongAdder 只会加法。LongAccumulator 允许传入自定义累积函数(求最大值、最小值、任意满足结合律的运算),内部同样是分摊思想——LongAdder 本质上就是它取「加法」的特例。需要滑动最大值这类统计时,直接用它,不必自己造轮子。
无锁化的武器库到此齐备。但业务里大量场景是「一段逻辑整体不能被打断」——单变量 CAS 管不住,synchronized 又想要更灵活的中断、超时、公平选项。下一篇进入整个 JUC 的地基工程:AQS,看一把显式锁是怎么从零造出来的。
评论 (0)