ConcurrentHashMap与LongAdder均采用分段思想优化高并发:通过哈希映射将全局竞争拆为局部竞争,以空间换时间、用最终一致性换吞吐量,适用于监控统计等容忍误差的场景。

ConcurrentHashMap 和 LongAdder 看似用途不同——一个存键值对,一个做累加计数——但它们共享同一种高并发优化的底层智慧:**把“一把锁管全局”的瓶颈,拆成“多把锁管局部”**。这种“分段”不是简单切分,而是用空间换时间、用最终一致性换吞吐量的设计权衡。
分段的本质是降低共享变量的竞争热度
AtomicInteger 或早期 HashMap 的性能瓶颈,根源在于所有线程争抢同一个内存地址:
- AtomicInteger.incrementAndGet() 底层是循环 CAS,100 个线程同时更新,99 个大概率自旋失败,CPU 白白空转;
- JDK 7 的 ConcurrentHashMap 用 Segment 分段,每个 Segment 是独立的 HashEntry 数组 + ReentrantLock,线程按 key 的 hash 落到不同段,写操作互不阻塞;
- LongAdder 则把一个 long 拆成 base + cells[],线程用 probe 值哈希定位到某个 Cell 更新,避免全挤在 base 上竞争。
分段结构都依赖哈希映射与懒加载
两者都不一上来就铺满资源,而是按需伸缩:
- ConcurrentHashMap 的 Segment 数组(JDK 7)或 Node 数组(JDK 8+)初始容量小,扩容触发条件明确(如 sizeCtl 阈值);
- LongAdder 的 cells[] 默认为 null,首次竞争失败才初始化,后续扩容也保守(仅当当前 cell 冲突严重且未达 CPU 核数上限时);
- 哈希方式都尽量让线程分布均匀:ConcurrentHashMap 用 key.hashCode() 高位扰动后取模;LongAdder 用 ThreadLocalRandom.probe 值做位运算映射,减少线程间哈希碰撞。
读操作的代价:从“即时快照”退让到“最终一致”
分段提升写性能,但读逻辑变得更重、更松:
- ConcurrentHashMap.size() 在 JDK 7 中需遍历所有 Segment 加锁求和;JDK 8 改为维护 baseCount + CounterCell[],sum() 同样要遍历数组累加,结果非强一致;
- LongAdder.sum() 遍历所有非空 Cell 并加上 base,过程中其他线程可能正往某个 Cell 写入新值,所以返回的是某一时刻的近似总和;
- 二者都不承诺“读写线性一致”,但对监控指标、QPS 统计、错误计数这类场景完全够用——你不需要知道第 123456789 次请求的确切序号,只需要知道这一秒大概处理了 1.2 万次。
适用边界清晰:分段不是万能解药
一旦需求超出“高写低读+容忍误差”的范围,分段优势就消失甚至成负担:
- 需要 compareAndSet、getAndIncrement 等原子组合语义?→ 回退 AtomicInteger / AtomicLong;
- 要求严格顺序或条件更新(如“余额 > 100 才扣减”)?→ 必须配合 synchronized、StampedLock 或数据库行锁;
- 读远多于写,或要求每次读都是精确快照?→ volatile + 读锁,或直接用普通 long + 外部同步;
- 生命周期极短(如单次 HTTP 请求内新建又丢弃)?→ 分段初始化开销反而拖累性能,不如 AtomicLong 轻量。

















