news 2026/5/11 23:22:42

Java原子累加器深度解析(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java原子累加器深度解析(一)

前言

在并发编程中,计数器是最常见的需求之一——统计 QPS、记录请求数、追踪错误次数。JDK 从 1.5 起提供了 `AtomicLong`,基于 CAS 实现无锁原子操作。但在高并发场景下,CAS 自旋失败率飙升,CPU 空转严重。于是 JDK 8 引入了 `LongAdder`,通过分散热点 的核心设计思想"空间换时间 + 分而治之",将单点竞争拆分为多 Cell 并发写入,吞吐量提升一个数量级。

一、AtomicLong——CAS 的经典实现

1.1 核心思想

`AtomicLong` 内部持有一个 `volatile long` 值,所有原子操作通过 `Unsafe.compareAndSwapLong`(CAS)完成。

CAS 的三要素:
- 内存地址(对象 + 字段偏移量)
- 期望值(expected)
- 新值(new)

CPU 原子指令(如 x86 的 `cmpxchg` + `lock` 前缀)保证:如果内存中的值与期望值相等,则更新为新值并返回 true;否则不做任何操作并返回 false。

// AtomicLong 核心字段(JDK 17 源码简化) public class AtomicLong extends Number implements java.io.Serializable { private static final long serialVersionUID = 1927816293512124184L; // Unsafe 实例——JDK 内部的"后门"类 private static final Unsafe unsafe = Unsafe.getUnsafe(); // value 字段在对象内存中的偏移量 private static final long valueOffset; // 核心:volatile 保证可见性 private volatile long value; static { try { valueOffset = unsafe.objectFieldOffset( AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } }

1.2 核心 API 与源码解读

// 直接读取 volatile 值——总是看到最新写入 public final long get() { return value; } // 直接写入 volatile 值——立即对其他线程可见 public final void set(long newValue) { value = newValue; } // 延迟写入:最终可见,但不保证立即可见。 // 内部使用 Unsafe.putOrderedLong,是一条 StoreStore 屏障, // 不强制刷新到主存,但保证本线程之前的写不会被重排到此之后。 // 适合:置零、取消状态等不需要立即被其他线程看到的场景。 public final void lazySet(long newValue) { unsafe.putOrderedLong(this, valueOffset, newValue); }

lazySet` 与 `set` 的关键区别:

// set():volatile 写,触发 StoreLoad 屏障 → 立即刷新到主存,其他线程立即可见 // lazySet():putOrderedLong → 只有 StoreStore 语义 → 不保证立即可见 // 典型使用场景:cancel 标志 volatile boolean running = true; // 线程 A:直接写 volatile,没必要——设 false 后不需要被立刻看到 // set(false) 太重了 // 用 AtomicBoolean.lazySet(false) 更好:最终会看到 false,不需要 StoreLoad 屏障
CAS 核心操作
// 最底层:比较并交换 public final boolean compareAndSet(long expectedValue, long newValue) { return unsafe.compareAndSwapLong(this, valueOffset, expectedValue, newValue); } // JDK 9+ 新增别名,语义完全相同 public final boolean weakCompareAndSet(long expectedValue, long newValue) { return unsafe.compareAndSwapLong(this, valueOffset, expectedValue, newValue); }

注意:`AtomicLong.weakCompareAndSet` 在 JDK 9 之前可能"伪失败"(spurious failure),但 JDK 9 起其实现与 `compareAndSet` 完全一致,底层均调用同一方法。两者的语义区别在于规范层面的约定,而非实际行为。

自旋加法(CAS 循环)
// getAndAdd 的经典 CAS 自旋 public final long getAndAdd(long delta) { long current, next; do { current = get(); // 1. 读取当前值 next = current + delta; // 2. 计算新值 } while (!compareAndSet(current, next)); // 3. CAS 尝试写入,失败则重试 return current; // 返回旧值 } // addAndGet 同理,返回新值 public final long addAndGet(long delta) { long current, next; do { current = get(); next = current + delta; } while (!compareAndSet(current, next)); return next; }
递增/递减
// 以下四个方法内部均委托给 getAndAdd / addAndGet public final long getAndIncrement() { return getAndAdd(1L); } public final long incrementAndGet() { return addAndGet(1L); } public final long getAndDecrement() { return getAndAdd(-1L); } public final long decrementAndGet() { return addAndGet(-1L); }
JDK 8 新增:函数式更新方法
// 原子地应用函数到当前值,返回旧值 public final long getAndUpdate(LongUnaryOperator updateFunction) { long prev, next; do { prev = get(); next = updateFunction.applyAsLong(prev); } while (!compareAndSet(prev, next)); return prev; } // 原子地应用函数到当前值,返回新值 public final long updateAndGet(LongUnaryOperator updateFunction) { long prev, next; do { prev = get(); next = updateFunction.applyAsLong(prev); } while (!compareAndSet(prev, next)); return next; } // 原子地应用累加函数(二元操作:当前值 + 参数 x),返回旧值 public final long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction) { long prev, next; do { prev = get(); next = accumulatorFunction.applyAsLong(prev, x); } while (!compareAndSet(prev, next)); return prev; } // 原子地应用累加函数,返回新值 public final long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction) { long prev, next; do { prev = get(); next = accumulatorFunction.applyAsLong(prev, x); } while (!compareAndSet(prev, next)); return next; }
函数式方法使用示例
AtomicLong counter = new AtomicLong(10); // 原子地执行 counter = counter * 2 counter.updateAndGet(v -> v * 2); // → 20 // 原子地执行 counter = max(counter, 5) counter.accumulateAndGet(5, Math::max); // → 20 // 原子地执行 counter = counter ^ 0xFF(异或),返回旧值 long old = counter.getAndAccumulate(0xFF, (v, x) -> v ^ x); System.out.println("old = " + old + ", new = " + counter.get()); // 输出:old = 20, new = 235(20 ^ 255 = 235)

1.3 CAS 的性能瓶颈

CAS 在低竞争下表现优异——大多数时候一次就成功。但当线程数增多时: CPU Core 1 CPU Core 2 CPU Core 3 CPU Core 4 │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │CAS 尝试 │ │CAS 尝试 │ │CAS 尝试 │ │CAS 尝试 │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ └──────────────────┼──────────────────┼──────────────────┘ ▼ ┌─────────────┐ │ volatile │ │ long value │ ← 所有线程竞争同一个内存位置 └─────────────┘ 竞争结果: - Core 1: CAS 成功 → 值变为 1 - Core 2: CAS 失败 → 总线上看到值变了 → 自旋重试 - Core 3: CAS 失败 → 自旋重试 - Core 4: CAS 失败 → 自旋重试

根本问题:所有线程修改同一个内存地址,CAS 写入后会导致其他 CPU 缓存行失效(cache line invalidation),引发大量总线流量。这就是"伪共享"的一个变种——所有核心争抢同一个缓存行的独占权。

实际测试:在 16 核机器上,100 个线程各执行 100 万次 `AtomicLong.incrementAndGet()`: - 1 个线程:~40ms - 4 个线程:~60ms - 16 个线程:~300ms - 64 个线程:~1200ms

性能退化接近线性——更多的线程不仅没有加速,反而在互相踩脚。

二、LongAdder——分散热点

2.1 设计思想

`LongAdder` 的核心灵感来自 `java.util.concurrent.ConcurrentHashMap` 的分段锁思想:

与其让所有线程争夺一把锁(或一个 CAS 变量),不如把热点数据拆分为多个 Cell,每个线程优先在自己的 Cell 上 CAS,最后求和。

Thread-0 Thread-1 Thread-2 Thread-3 Thread-4 │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌─────────┐┌─────────┐┌─────────┐┌─────────┐┌─────────┐ │ Cell[0] ││ Cell[1] ││ Cell[2] ││ Cell[3] ││ Cell[4] │ │ value=3 ││ value=7 ││ value=2 ││ value=4 ││ value=1 │ └────┬────┘└────┬────┘└────┬────┘└────┬────┘└────┬────┘ │ │ │ │ │ └───────────┴───────────┴───────────┴───────────┘ │ ▼ sum() = 3+7+2+4+1 = 17

- 写入时:只竞争一个 Cell,不同线程大概率哈希到不同 Cell。
- 读取时:遍历所有 Cell 累加,但读取不需要加锁(各 Cell 的 value 是 volatile 的)。
- trade-off:用空间换时间,用近似一致性(sum 不是快照)换吞吐量。

2.2 核心 API

public class LongAdder extends Striped64 implements Serializable { // 加 1(最常用) public void add(long x); public void increment(); // 等价于 add(1) public void decrement(); // 等价于 add(-1) // 汇总——遍历所有 Cell 累加 public long sum(); // sum() + 将所有 Cell 归零(不是原子操作,但对于统计场景足够了) public long sumThenReset(); // 归零 public void reset(); // 转为 int / long / float / double(可能截断或丢失精度) public int intValue(); public long longValue(); // 等价于 sum() public float floatValue(); public double doubleValue(); }
基础使用示例:
LongAdder requestCount = new LongAdder(); // 100 个线程各加 1000 次 ExecutorService pool = Executors.newFixedThreadPool(100); for (int i = 0; i < 100; i++) { pool.submit(() -> { for (int j = 0; j < 1000; j++) { requestCount.increment(); } }); } pool.shutdown(); pool.awaitTermination(5, TimeUnit.SECONDS); System.out.println("总请求数: " + requestCount.sum()); // → 100000

2.3 源码分析

LongAdder 本身的代码非常简洁——它把复杂性完全委托给了父类 `Striped64`。 // JDK 17 的 LongAdder 源码(精简,保留核心逻辑) public class LongAdder extends Striped64 implements Serializable { private static final long serialVersionUID = 7249069246863182397L; // ── 写入操作 ── public void add(long x) { Cell[] cs; long b, v; int m; Cell c; // 1. cells 不为 null,说明已经有竞争,直接走 Cell // 2. casBase 尝试直接 CAS 修改 base // 如果失败(有竞争),同样进入 Cell 逻辑 if ((cs = cells) != null || !casBase(b = base, b + x)) { // uncontended 标志:本线程进入此方法时是否检测到竞争 boolean uncontended = true; // 条件 1: cs == null —— cells 尚未初始化 // 条件 2: m = cs.length - 1; m < 0 —— cells 为空数组 // 条件 3: getProbe() 哈希到的 Cell 为 null —— Cell 尚未创建 // 条件 4: !cas (尝试 CAS 该 Cell) —— 有竞争 if (cs == null || (m = cs.length - 1) < 0 || (c = cs[getProbe() & m]) == null || !(uncontended = c.cas(v = c.value, v + x))) { // 都失败→进入 Striped64.longAccumulate,处理 Cell 初始化/扩容 longAccumulate(x, null, uncontended); } } } public void increment() { add(1L); } public void decrement() { add(-1L); } // ── 读取操作 ── public long sum() { Cell[] cs = cells; long sum = base; // 先取 base if (cs != null) { for (Cell c : cs) { // 遍历所有 Cell if (c != null) { sum += c.value; // 累加 volatile 值 } } } return sum; } // 汇总并重置——非原子的,但在统计场景下足够 public long sumThenReset() { Cell[] cs = cells; long sum = base; base = 0L; // 重置 base if (cs != null) { for (Cell c : cs) { if (c != null) { sum += c.value; c.value = 0L; // 逐个重置 Cell } } } return sum; } public void reset() { Cell[] cs = cells; base = 0L; if (cs != null) { for (Cell c : cs) { if (c != null) { c.value = 0L; } } } } // ── 序列化 ── // LongAdder 序列化时实际上序列化的是 sum() 的等效值 // 反序列化后得到一个初始值为 sum() 的普通 LongAdder private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeLong(sum()); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); cellsBusy = 0; cells = null; base = s.readLong(); } }

2.4 add() 的执行路径分析

`add(long x)` 有四条执行路径,按竞争程度递增:
add(x)

├─ [路径 1] cells == null && casBase(base, base+x) 成功
│ → 无竞争,一次 CAS 搞定,返回

├─ [路径 2] cells != null && Cell 存在 && casCell 成功
│ → 有历史竞争,但本线程哈希到的 Cell 恰好空闲

├─ [路径 3] cells != null && Cell 存在 && casCell 失败
│ → 本线程的 Cell 也有竞争,需要扩容或重哈希
│ → 进入 longAccumulate()

└─ [路径 4] cells == null && casBase 失败 → cells 未初始化
或 cells != null && Cell == null → Cell 未创建
→ 进入 longAccumulate()

性能对比:
路径 1:≈ AtomicLong.increment() 性能(无竞争)
路径 2:≈ AtomicLong.increment() 性能,但分到不同 Cell 后各线程独立 CAS
路径 3:自动扩容,将热点继续拆分
路径 4:首次发生竞争,初始化 cells 数组

三、Striped64——LongAdder 的核心引擎

3.1 整体架构

`Striped64` 是所有"分段累加器"的抽象基类。它的子类有:

子类功能
LongAdder加法累加(初始值 0,操作符 +)
LongAccumulator自定义累加函数(如求最大值、乘积)
DoubleAdder双精度浮点加法累加
DoubleAccumulator双精度浮点自定义累加
// Striped64 类层次结构 abstract class Striped64 extends Number { // ── 内部类 ── @jdk.internal.vm.annotation.Contended // JDK 9+ 防止伪共享 static final class Cell { volatile long value; Cell(long x) { value = x; } // CAS 设置值 final boolean cas(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } // 字段偏移量(静态初始化块) private static final long valueOffset; private static final Unsafe UNSAFE; static { /* Unsafe + objectFieldOffset */ } } // ── 核心字段 ── transient volatile Cell[] cells; // Cell 数组,2 的幂次方大小 transient volatile long base; // 无竞争时直接操作的基础值 transient volatile int cellsBusy; // 自旋锁:0=空闲, 1=被占用(CAS 获取) // ── 核心方法 ── final boolean casBase(long cmp, long val); // CAS base final boolean casCellsBusy(); // CAS cellsBusy 获取锁 final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended); final void doubleAccumulate(double x, DoubleBinaryOperator fn, boolean wasUncontended); // 获取线程探针值(用于哈希到 Cell) static final int getProbe(); }

3.2 Cell 与伪共享防护

伪共享(False Sharing)是什么?

CPU 缓存以缓存行(Cache Line,通常 64 字节)为单位。如果两个独立的变量落在同一个缓存行,一个核修改其中变量会导致另一个核的整个缓存行失效——即使它访问的是另一个变量。

没有 @Contended 时: ┌────────────────── 64 字节缓存行 ──────────────────┐ │ Cell[0].value (8B) │ Cell[1].value (8B) │ ... │ └────────────────────┴────────────────────┴────────┘ ↑ Core 0 写入 ↑ Core 1 写入 └──────────────────────┘ 互不相关的两个 Cell 互相使对方缓存行失效! 加上 @Contended 后: ┌────── 64B ──────┐ ┌────── 64B ──────┐ │ Cell[0] + 填充 │ │ Cell[1] + 填充 │ └─────────────────┘ └──────────────── ┘ ↑ Core 0 独占 ↑ Core 1 独占——互不干扰
JDK 内部实现(源码简化): @jdk.internal.vm.annotation.Contended static final class Cell { volatile long value; Cell(long x) { value = x; } // ... CAS 方法 }

@Contended注解告诉 JVM:为这个类的实例前后各填充 128 字节,确保每个 Cell 独占一个缓存行。JDK 8 中还需要 `-XX:-RestrictContended` 启动参数来启用此注解,JDK 9 起内部类默认生效。

在没有 `@Contended` 之前(JDK 8 早期),Striped64 通过手动添加 padding 字段来填充: // JDK 8 的 Striped64.Cell 手写 padding 版本示意 static final class Cell { volatile long value; // 手动填充 64 字节以规避伪共享 volatile long p1, p2, p3, p4, p5, p6; // value(8) + 6*8(padding) = 56 bytes + 对象头 ≈ 刚好一个缓存行 }

3.3 longAccumulate——最核心的方法

该方法处理所有"CAS base 失败"后的逻辑,包括:Cell 数组初始化、Cell 扩容、Cell 创建、冲突重哈希。

// JDK 17 Striped64.longAccumulate — 带详细注释版 final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) { int h; // 线程探针值,用作哈希到 Cell 的索引 if ((h = getProbe()) == 0) { // 首次使用——初始化探针值 ThreadLocalRandom.current(); // 强制初始化 h = getProbe(); wasUncontended = true; // 标记为未竞争(新探针无法判断历史) } boolean collide = false; // 冲突标志——上次是否发生了竞争 done: for (;;) { Cell[] cs; Cell c; int n; long v; // ── 情况1:cells 已初始化 ── if ((cs = cells) != null && (n = cs.length) > 0) { // 1a. 哈希到的槽位为空——创建新 Cell if ((c = cs[(n - 1) & h]) == null) { if (cellsBusy == 0) { // 锁空闲 → 尝试获锁 Cell r = new Cell(x); // 乐观创建 Cell 对象 if (cellsBusy == 0 && casCellsBusy()) { // 获锁成功,再次确认槽位仍为空 try { Cell[] rs; int m, j; if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { rs[j] = r; // 放入新 Cell break done; // 完成 } } finally { cellsBusy = 0; // 释放锁 } continue; // 槽位已被其他线程填充,重试 } } collide = false; // 未碰撞(槽位为空是正常状态) } // 1b. wasUncontended 为 false → 提高碰撞阈值,再给一次机会 else if (!wasUncontended) { wasUncontended = true; // 仅这一次,下次不走了 } // 1c. CAS 尝试写入当前 Cell else if (c.cas(v = c.value, (fn == null) ? v + x : fn.applyAsLong(v, x))) { break done; // CAS 成功! } // 1d. 扩容检查:数组已达 CPU 核心数上限,或数组已被其他线程扩容 else if (n >= NCPU || cells != cs) { collide = false; // 无法扩容,重置冲突标志 } // 1e. 冲突标志——连续两次 CAS 失败才扩容 else if (!collide) { collide = true; // 第一次冲突→记录下来,下次再冲突就扩容 } // 1f. 真正扩容——CAS 获取锁,然后 double cells else if (cellsBusy == 0 && casCellsBusy()) { try { if (cells == cs) { // 确认未被其他线程修改 Cell[] newCells = new Cell[n << 1]; // 翻倍 System.arraycopy(cs, 0, newCells, 0, n); cells = newCells; // 原子替换引用 } } finally { cellsBusy = 0; } collide = false; // 扩容完成,重置冲突标志 continue; // 用新数组重试 } // 1g. 重新哈希 → 换一个 Cell h = advanceProbe(h); } // ── 情况2:cells 未初始化,尝试初始化 ── else if (cellsBusy == 0 && cells == cs && casCellsBusy()) { try { if (cells == cs) { // 双重检查 // 初始大小 = 2(最小),不可超过 NCPU Cell[] newCells = new Cell[2]; newCells[h & 1] = new Cell(x); // 只创建一个 Cell 放入 cells = newCells; break done; } } finally { cellsBusy = 0; } } // ── 情况3:cellsBusy == 1(初始化/扩容进行中),回退到 CAS base ── else if (casBase(v = base, (fn == null) ? v + x : fn.applyAsLong(v, x))) { break done; // 成功落到 base 上 } } }

方法调用参数解析:

`longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended)`
- `x`:增量值
- `fn`:自定义累加函数,LongAdder 传入 `null` 时使用默认加法
- `wasUncontended`:调用前 CAS Cell 是否成功

扩容策略的两阶段缓冲:

阶段行为
第一次 Cell CAS 失败`collide = false`,设 `wasUncontended = false`,再给一次机会
第二次 Cell CAS 失败`collide = true`,标记冲突但暂不扩容
第三次 Cell CAS 失败触发扩容,数组翻倍(直到 `NCPU` 上限)

这种三次失败才扩容的设计避免了不必要的数据迁移。因为偶尔的 CAS 冲突可能只是瞬时现象。

扩容上界 NCPU:
// Striped64 中的常量
static final int NCPU = Runtime.getRuntime().availableProcessors();

Cell 数量不会超过 CPU 核心数。原因:执行的线程数 ≤ 可运行线程数 ≤ CPU 核心数(近似),超过也没有意义。

3.4 线程探针与哈希

每个线程有一个 `probe`(探针)值,存储在 `Thread.threadLocalRandomProbe` 字段中, 由 `ThreadLocalRandom` 负责初始化。这个探针决定了线程映射到哪个 Cell: // 获取探针并映射到 Cell 索引 int m = cells.length - 1; // m 是 (2^n - 1),相当于位掩码 int index = getProbe() & m; // 取低位作索引 // 冲突时更新探针——相当于换一个桶 static final int advanceProbe(int probe) { probe ^= probe << 13; // 使用 xorshift 算法生成伪随机数 probe ^= probe >>> 17; probe ^= probe << 5; UNSAFE.putInt(Thread.currentThread(), PROBE_OFFSET, probe); return probe; }

为什么不用 `ThreadLocalRandom` 直接生成随机索引?
因为探针存储在 `Thread` 对象上,无需额外分配,且 xorshift 计算速度极快(几个 CPU 周期)。

四、LongAccumulator——LongAdder 的通用化

4.1 设计动机

`LongAdder` 只支持加法。但有时我们需要其他聚合——统计最大值、最小值、乘法、甚至自定义函数。`LongAccumulator` 应运而生: // LongAdder 本质上是 LongAccumulator 的特化: // new LongAdder() ≈ new LongAccumulator((x, y) -> x + y, 0L)

4.2 核心 API

public class LongAccumulator extends Striped64 implements Serializable { // 构造函数:指定累加函数和初始值 // accumulatorFunction: 如何将新值与旧值合并 // identity: 初始值(每个 Cell 初始化为该值) public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) { ... } // 累加一个值 public void accumulate(long x); // 获取当前聚合后的值(遍历所有 Cell,使用 accumulatorFunction 合并) public long get(); // 获取后重置(重置为 identity) public long getThenReset(); // 重置为 identity public void reset(); // 转换 public int intValue() { return (int)get(); } public long longValue() { return get(); } public float floatValue(){ return (float)get(); } public double doubleValue(){ return (double)get(); } }

与 LongAdder 的关键区别:

特性LongAdderLongAccumulator
累加函数固定加法自定义 `LongBinaryOperator`
初始值固定 0自定义 `identity`
汇总方式直接相加各 Cell用 `accumulatorFunction` 合并 base 和各 Cell
get() 方法`sum()` 逐 Cell 相加用函数合并 base 和每个 Cell

4.3 典型应用场景

// ── 场景1:并发求最大值 ── LongAccumulator maxAccumulator = new LongAccumulator(Math::max, Long.MIN_VALUE); ExecutorService pool = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { final long val = ThreadLocalRandom.current().nextLong(1000); pool.submit(() -> { maxAccumulator.accumulate(val); System.out.println(Thread.currentThread().getName() + " produced: " + val); }); } pool.shutdown(); pool.awaitTermination(2, TimeUnit.SECONDS); System.out.println("最大值: " + maxAccumulator.get()); // ── 场景2:并发求最小值 ── LongAccumulator minAccumulator = new LongAccumulator(Math::min, Long.MAX_VALUE); // ── 场景3:并发求乘积 ── LongAccumulator productAccumulator = new LongAccumulator((a, b) -> a * b, 1L); // ── 场景4:统计非零元素个数 ── LongAccumulator nonZeroCounter = new LongAccumulator( (count, x) -> count + (x != 0 ? 1 : 0), 0L); // ── 场景5:自定义复杂聚合(示例:只累加正数) ── LongAccumulator positiveSum = new LongAccumulator( (acc, x) -> acc + (x > 0 ? x : 0), 0L);

4.4 源码实现要点

// LongAccumulator 的核心方法(JDK 17 源码简化) public class LongAccumulator extends Striped64 implements Serializable { private final LongBinaryOperator function; private final long identity; public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) { this.function = accumulatorFunction; base = this.identity = identity; } public void accumulate(long x) { Cell[] cs; long b, v, r; int m; Cell c; if ((cs = cells) != null || ((r = function.applyAsLong(b = base, x)) != b && !casBase(b, r))) { boolean uncontended = true; if (cs == null || (m = cs.length - 1) < 0 || (c = cs[getProbe() & m]) == null || !(uncontended = c.cas(v = c.value, function.applyAsLong(v, x)))) { longAccumulate(x, function, uncontended); } } } public long get() { Cell[] cs = cells; long result = base; if (cs != null) { for (Cell c : cs) { if (c != null) result = function.applyAsLong(result, c.value); } } return result; } public void reset() { Cell[] cs = cells; base = identity; if (cs != null) { for (Cell c : cs) { if (c != null) c.value = identity; } } } public long getThenReset() { Cell[] cs = cells; long result = base; base = identity; if (cs != null) { for (Cell c : cs) { if (c != null) { result = function.applyAsLong(result, c.value); c.value = identity; } } } return result; } }

accumulate中值得注意的设计细节:

// 当 base == function.applyAsLong(base, x) 时,不会进入 Cell 逻辑 // 这是针对特殊函数的优化 // 例如:LongAccumulator(Math::max, Long.MIN_VALUE) // 如果 x <= base(base 已经是目前最大值), // function.applyAsLong(base, x) == base, // 什么都不用做——无需 CAS,无需 Cell if ((cs = cells) != null || ((r = function.applyAsLong(b = base, x)) != b && !casBase(b, r))) { // 进入 Cell 逻辑 }

这个短路判断非常巧妙:如果当前值 `x` 对聚合结果没有影响(比如最大值已足够大),直接跳过,零开销。

4.5 LongAccumulator 的局限性

`LongAccumulator` 不保证结合律下的正确性。举例: // get() 的合并顺序:base → cells[0] → cells[1] → ... // 不是 cells 的任意组合顺序 // 对于满足结合律的函数(+、max、min),顺序不影响结果 // 对于不满足结合律的函数,get() 的结果取决于 Cell 遍历顺序 // 正确用法(结合律成立): new LongAccumulator((a, b) -> a + b, 0); // 加法满足结合律 new LongAccumulator(Math::max, 0); // max 满足结合律 // 有风险(结合律不成立): new LongAccumulator((a, b) -> a - b, 0); // 减法不满足结合律!

五、DoubleAdder 与 DoubleAccumulator

JDK 8 同时提供了 `double` 版本的累加器,其设计思想与 `LongAdder`/`LongAccumulator` 完全对称。

5.1 DoubleAdder

DoubleAdder adder = new DoubleAdder(); adder.add(3.14); adder.add(2.71); System.out.println(adder.sum()); // → 5.85

底层用 `Double.doubleToRawLongBits` / `Double.longBitsToDouble` 将 double 转为 long,然后用 CAS 操作 long 值。原理和执行路径与 `LongAdder` 一致。

5.2 DoubleAccumulator

// 并发求浮点数的最大值 DoubleAccumulator maxTracker = new DoubleAccumulator(Math::max, Double.NEGATIVE_INFINITY);

以上均为个人观点!

以上均为个人观点!

以上均为个人观点!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 23:19:31

2026届最火的五大AI辅助写作平台横评

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 当下学术出版情形里&#xff0c;知网针对AI生成内容辨别的机制不断更新&#xff0c;有关降AI…

作者头像 李华
网站建设 2026/5/11 23:18:53

从“修模”到“智模”:DP-Modeler如何重塑无人机实景三维模型的生产流程

1. 无人机航测模型的痛点与挑战 第一次接触无人机航测实景三维模型时&#xff0c;我被那些扭曲的桥梁、破碎的水面和凹凸不平的道路震惊了。这就像用单反相机拍出RAW格式照片后直接扔给客户——虽然数据原始完整&#xff0c;但根本没法直接用。传统模型修饰工作就像拿着Photosh…

作者头像 李华
网站建设 2026/5/11 23:17:19

Qt QML 模块化进阶:qmldir 实战避坑与高效配置

1. qmldir模块化管理的核心价值 在QML项目规模逐渐扩大时&#xff0c;组件管理往往会变得混乱不堪。我曾经接手过一个中型无人机控制项目&#xff0c;里面散落着200多个QML文件&#xff0c;开发者不得不通过冗长的相对路径来引用组件&#xff0c;每次修改文件位置都像在玩多米诺…

作者头像 李华
网站建设 2026/5/11 23:09:05

加州自动驾驶测试报告解读:数据背后的技术演进与行业趋势

1. 从加州数据看自动驾驶的“成绩单”&#xff1a;2021年测试报告深度解读每年年初&#xff0c;自动驾驶圈子里不少人都会习惯性地去翻看一份来自美国加州的“成绩单”——加州机动车辆管理局发布的年度自动驾驶车辆测试报告。这份报告就像一份公开的“期中考试”排名&#xff…

作者头像 李华
网站建设 2026/5/11 23:08:20

STM32CUBEMX实战指南:串口DMA高效收发与自定义打印函数优化

1. 串口DMA基础与STM32CubeMX配置 第一次用STM32CubeMX配置串口DMA时&#xff0c;我对着密密麻麻的选项差点崩溃。后来发现只要掌握几个关键点&#xff0c;5分钟就能搞定稳定可靠的DMA通信。先解释下为什么需要DMA&#xff1a;当你用传统方式通过串口发送"Hello World&quo…

作者头像 李华
网站建设 2026/5/11 23:07:26

3D打印螺纹终极指南:Fusion 360自定义配置完整解决方案

3D打印螺纹终极指南&#xff1a;Fusion 360自定义配置完整解决方案 【免费下载链接】CustomThreads Fusion 360 Thread Profiles for 3D-Printed Threads 项目地址: https://gitcode.com/gh_mirrors/cu/CustomThreads 引言&#xff1a;为什么您打印的螺纹总是"卡死…

作者头像 李华