伪共享是CPU缓存行机制导致的性能瓶颈,不破坏正确性但严重降低CAS吞吐量;解决核心是内存布局隔离——通过手动填充或@Contended注解使高竞争字段独占64字节缓存行。

Java 无锁编程中,伪共享是拖慢 CAS 吞吐量的隐形瓶颈——不是代码写错了,而是变量“住得太近”。解决它的核心不是改算法,而是调整内存布局,让高竞争字段独占缓存行。真正有效且生产可用的方式只有两种:手动字段填充和 @Contended 注解,二者都与 Unsafe 无关。
为什么填充能提升 CAS 吞吐量
当多个线程频繁对不同对象的 volatile 字段(如 AtomicLong.value)执行 CAS 操作时,若这些字段被 JVM 分配到同一缓存行(通常是 64 字节),每次 CAS 写入都会触发缓存一致性协议(如 MESI),导致其他核心的对应缓存行失效。线程不得不反复重载缓存行,形成“缓存行颠簸”,CAS 实际成功率下降、重试增多、吞吐暴跌。填充的作用就是物理隔离,让每个关键字段独享一个缓存行,消除这种无效同步。
手动填充:用 long 字段“围住”关键变量
这是最稳定、无需 JVM 参数、兼容所有 JDK 版本的做法。原理是利用 long 占 8 字节,7 个 long = 56 字节,加上目标字段(如 volatile long 占 8 字节),刚好填满 64 字节缓存行;再加一组后置填充,防止后续字段挤入同一行。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 关键字段必须是 volatile 或原子类的底层 value 字段(如
AtomicLong的value),否则无法保证可见性 - 填充字段类型推荐
long(避免 JVM 优化掉无用字段),命名如p1–p7和p8–p14 - JVM 可能重排字段顺序,所以填充必须放在目标字段前后,形成“夹心结构”,不能只靠前置或只靠后置
- 示例结构:
class PaddedCounter { long p1, p2, p3, p4, p5, p6, p7; // 前置 56 字节 public volatile long value = 0L; // 关键 CAS 字段(8 字节) long p8, p9, p10, p11, p12, p13, p14; // 后置 56 字节 }
@Contended 注解:JVM 自动对齐,更简洁但需启用
JDK 8 引入的 @sun.misc.Contended 可让 JVM 在加载类时自动在标注字段前后插入填充(默认 128 字节,可配置)。它比手动填充更可靠,不受字段重排影响,且语义清晰。
立即学习“Java免费学习笔记(深入)”;
- 必须配合 JVM 启动参数:
-XX:-RestrictContended(JDK 8/9)或--add-opens java.base/jdk.internal.vm=ALL-UNNAMED(JDK 17+) - 只能标注字段,不能标注整个类;多个字段需分别标注
- 适用于高竞争、对性能极度敏感的场景(如 RingBuffer 的 head/tail)
- 示例:
class RingBuffer { @sun.misc.Contended volatile long head; @sun.misc.Contended volatile long tail; }
实际使用中的关键注意点
填充不是万能银弹,用错反而伤性能:
- 不要对低频更新字段填充——内存浪费,GC 压力增大
- 确认目标 CPU 缓存行大小(x86_64 通常是 64 字节,ARM 或某些服务器可能是 128 字节)
- 避免过度填充:比如为 4 字节 int 字段填 60 字节 long,既浪费又可能因对齐规则导致实际占用更大
- 填充无法解决真共享(多个线程改同一变量),它只针对伪共享;真共享仍需锁、CAS 重试或设计重构
- 结合
lazySet使用效果更佳:对只需最终一致性的状态字段(如关闭标志),用lazySet避免写屏障,进一步减少缓存污染


















