Java LongAdder使用与应用实战
目录
- 深入核心原理
- ️ 实战应用示例
- ⚠️ 使用注意事项与最佳实践
- 选型指南:LongAdder vs. AtomicLong
- 总结
LongAdder是 Java 并发编程中为高并发计数而生的利器。下面这张表格能帮助你快速把握其全貌,之后我们再深入细节和实战。
| 特性维度 | 说明 |
|---|---|
| 设计目标 | 高并发场景下的高性能计数/累加操作,优化多线程写竞争 。 |
| 核心思想 | 分而治之,分散热点:将单一变量的竞争压力分散到多个单元(base+ Cell[]),以空间换取时间 。 |
| 关键数据结构 | base(基础值) 和 Cell[](单元数组) 。 |
| 写入流程 | 1. 无竞争或低竞争时,CAS 操作直接更新 base。 2. 竞争激烈时,线程通过哈希映射到 Cell[]中的某个单元进行更新,大幅减少冲突 。 |
读取流程 (sum()) | 返回 base与所有 Cell单元值的累加和。此操作不保证强一致性,是最终一致性的,因为在求和过程中可能有其他线程正在更新 。 |
| 主要优点 | 高并发写入性能远超 AtomicLong,有效减少 CAS 空自旋,避免高竞争下的性能骤降 。 |
| 主要缺点 | 更高的内存消耗;读取操作 (sum()) 非原子快照,是最终一致性的;不支持 compareAndSet等原子条件更新操作 。 |
| 典型应用场景 | 高频统计计数器(如 API 调用次数、点击量)、监控指标收集、频率统计等“写多读少”且对读的实时精确性要求不高的场景 。 |
深入核心原理
要理解 LongAdder的高性能,需要深入其内部机制。
分散热点与动态扩容
初始时,所有线程都尝试通过 CAS 操作更新
base变量。当并发加剧,某个线程 CAS 更新base失败时,系统会初始化一个Cell数组(默认php大小为 2)。每个线程会根据其唯一的哈希值(探针,Probe)被映射到数组的某个槽位(Cell),然后对该槽位内的值进行更新 。随着竞争持续,如果线程在指定的Cell上更新仍然失败,Cell数组会进行扩容(通常翻倍),直到达到与 CPU 核数相当的水平,以进一步分散竞争 。这种设计将针对单一内存地址的激烈竞争,转化为对多个内存地址的相对平和的访问。解决伪共享
Cell类使用@sun.misc.Contended注解进行填充,以避免伪共享(False Sharing)。伪共享是指多个看似不相关的变量因位于同一个 CPU 缓存行中,当一个处理器更新其中一个变量时,会导致整个缓存行失效,其他处理器即使使用该行内的其他变量,也需要重新从内存加载,造成性能损失。@Contended注解确保每个Cell对象独立占据一个缓存行,从而提升缓存效率 。核心方法流程
add(long x)方法是其核心 :- 首先检查
cells数组是否已初始化。若未初始化,则尝试直接js CAS 更新base字段。 - 若 CAS 更新
base失败(表明出现竞争),则进入冲突处理逻辑。 - 检查
cells数组是否已初始化、当前线程映射的Cell槽位是否存在、以及尝试 CAS 更新该Cell的值。 - 如果以上任意一步失败,则进入更复杂的
longAccumulate方法。该方法会处理cells数组的初始化、扩容,以及为线程重新计算哈希值以寻找新的空闲槽位,确保更新最终能够完成 。
sum()方法遍历cells数组(如果已初始化),将所有非空Cell的值与base相加返回。由于此操作没有加锁,在并发更新时返回的是某个时刻的近似总值,具备最终一致性而非强一致性 。- 首先检查
️ 实战应用示例
LongAdder非常适用于以下场景:
API 请求统php计与监控
可以轻松统计服务的请求量、成功/失败次数、总耗时等指标 。
public class ApiRequestdArwIbSxNOMonitor { private final LongAdder requestCount = new LongAdder(); private final LongAdder totalLatency = new LongAdder(); public void recordRequest(long latency) { requestCount.increment(); totalLatency.add(latency); } public MonitoringSnapshot getSnapshot() { // 注意:sum() 获取的是瞬时近似值 return new MonitoringSnapshot(requestCount.sum(), totalLatency.sum()); } }结合 ConcurrentHashMap 进行频率统计
这是一种常见且高效的模式,用于统计元素出现次数 。
ConcurrentMap<String, LongAdder> freqMap = new ConcurrentHashMap<>(); public void count(String word) { // 如果键不存在,则原子性地放入一个新的 LongAdder freqMap.computeIfAbsent(word, k -> new LongAdder()) .increment(); // 然后递增 } // 获取某个词的频率 long frequency = freqMap.getOrDefault(word, new LongAdder()).sum();
⚠️ 使用注意事项与最佳实践
- 理解一致性语义:
LongAdder的sum()方法是最终一致性的。如果你的业务场景要求在任何时刻读取都必须是完全精确的值(例如金融账户余额),那么AtomicLong或锁机制更为合适 。 - 关注内存占用:
Cell数组和避免伪共享的填充会带来比AtomicLong更高的内存开销。在内存受限或并发度不高的环境中,需要权衡利弊 。 - 避免频繁调用
sum():sum()方法需要遍历Cell数组,在数组较大时有一定开销。应避免在性能关键路径中频繁调用 。 - 重置操作:
reset()方法将base和所有Cell置零,但此操作非原子性。通常仅在确定没有并发更新时(如一个统计周期结束清零时)使用 。
选php型指南:LongAdder vs. AtomicLong
| 场景 | 推荐选择 | 理由 |
|---|---|---|
| 极高并发写入,对读的实时精确性要求不高(如统计、监控) | LongAdder | 写吞吐量极高,通过分散竞争避免性能瓶颈 。 |
| 低并发环境,或需要频繁读取精确瞬时值(如序列号生成、状态标志) | AtomicLong | 读取 (get()) 是强一致性的单次 volatile 读,性能极高;接口丰富,支持 compareAndSet等复杂原子操作 。 |
| 需要复杂的累加操作(如求最大值、最小值) | LongAccumulator | LongAdder是 LongAccumulator的一个特例(专用于加法)。LongAccumulator允许传入自定义二元运算符,功能更灵活 。 |
总结
LongAdder是 Java 并发工具包中“分而治之”思想的杰出代表。它通过空间换时间,巧妙地化解了高并发下的写入竞争,在统计、监控等“写多读少”的场景下表现卓越。
选择 LongAdder的关键在于明确:你是否愿意用读取操作的强一致性和更高的内存开销,来换取极高的并发写入性能。
到此这篇关于Java LongAdder使用与应用实战的文章就介绍到这了,更多相关Java LongAdder使用内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论