java中cas操作易引发总线风暴,应通过分段计数(如longadder)、批处理预占、状态轮询优化、lazyset降频、避免伪原子陷阱及threadlocal隔离等手段降低争用与缓存行竞争。

Java 中 CAS 操作本身不“避免”总线风暴,它恰恰是风暴的常见诱因之一;真正有效的做法,是通过降低 CAS 的争用密度、分散热点、减少跨核缓存行竞争来压低总线流量。
用分段计数替代单点 CAS
全局计数器(如 AtomicLong)在高并发下所有线程都抢同一个内存地址,每次 CAS 失败重试都会触发 MESI 协议广播,造成缓存行频繁失效。LongAdder 就是为此设计:
- 内部维护一个
Cell[]数组,每个线程优先尝试更新自己哈希定位到的 cell,冲突才退到 base 字段 - cell 之间用
@Contended隔离,确保不共享同一缓存行(64 字节),杜绝伪共享 - 扩容按需进行,上限通常为 CPU 核心数,避免过度分配但足够打散压力
减少 CAS 触发频次
不是所有场景都需要每操作一次就同步一次。可从执行节奏上降频:
- 批处理预占:比如 ID 生成,先
addAndGet(1000)拿一段号段,再本地递增使用,把 1000 次 CAS 压缩为 1 次 - 状态轮询改挂起:避免
while (!done) Thread.yield()这类空转读 volatile,换成LockSupport.parkNanos(1)或条件等待 - 非强一致场景用 lazySet:对仅需后续读可见、不需立即刷主存的标志位(如关闭信号),用
atomicInt.lazySet(1)替代set(1),跳过写屏障
避免误入“伪原子陷阱”
很多总线压力其实来自对原子类的不当使用,而非原子性本身有问题:
- 别在
parallelStream().forEach()里反复调counter.incrementAndGet()——每条数据一次 CAS,万级数据就是万次总线锁 - 不要用
AtomicReference<list></list>然后get().add(x),get 是原子的,add 不是,且 List 非线程安全,还白占 CAS 开销 - 统计类指标尽量移到
collect()阶段聚合,利用并行流内置归并(如Collectors.collectingAndThen(..., LongAdder::sum))
结合线程本地化进一步降压
当业务允许误差或最终一致性时,可叠加一层线程隔离:
- 用
ThreadLocal<longadder></longadder>,每个线程维护自己的累加器,最后统一sumThenReset() - 比全局 LongAdder 更进一步减少跨线程竞争,尤其适合中间统计、采样、日志计数等非关键路径
- 注意内存泄漏风险:配合
remove()清理,或使用弱引用 wrapper
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











