伪共享是CPU缓存行机制与Java对象内存布局共同导致的性能陷阱,不破坏程序正确性但严重降低高并发吞吐量;其根源在于64字节缓存行内多个变量被不同线程修改时触发MESI协议频繁失效同步,@Contended注解通过填充字节使关键字段独占缓存行来缓解该问题。

伪共享不是代码逻辑错误,而是 CPU 缓存行为与 Java 对象内存布局共同作用产生的性能陷阱。它不改变程序正确性,却会大幅拖慢高并发场景下的吞吐量。
缓存行是伪共享发生的物理基础
CPU 并不按字节访问内存,而是以缓存行为单位加载数据,主流 x86 架构下一行通常是 64 字节。当一个线程修改某个字段时,整个缓存行会被标记为“已修改”(dirty),其他 CPU 核心上该缓存行的副本随即失效。哪怕另一个线程只读写同一缓存行里的另一个无关字段,也必须重新从 L2/L3 或主存加载整行——这就是伪共享的起点。
例如:
- LongAdder 的 Cell 数组中多个 Cell 实例若在堆上连续分配,它们的 value 字段可能落在同一缓存行;
- ConcurrentHashMap 的 CounterCell 若未隔离,多个计数器变量也会互相干扰。
@Contended 是 JVM 层面的内存布局干预
@Contended 不做同步、不改逻辑,只让 JVM 在对象布局阶段主动“拉开距离”:
- 在被标注字段(如
Cell.value)前后插入填充字节,确保该字段独占至少一个完整缓存行; - 填充宽度默认约 128 字节(前后各 ~64 字节),可由
-XX:ContendedPaddingWidth=64显式指定; - 生效需满足硬性条件:
-XX:+UnlockExperimentalVMOptions -XX:-RestrictContended,且必须使用 JDK 内置注解(jdk.internal.vm.annotation.Contended,JDK 9+)或@sun.misc.Contended(JDK 8); - 仅对实例字段有效,static 字段、bootstrap classloader 加载的类(如 java.*)不支持。
它解决的是硬件级副作用,不是竞争逻辑
伪共享不是线程争抢锁,而是缓存一致性协议(如 MESI)引发的隐式同步开销。@Contended 的效果体现在:
立即学习“Java免费学习笔记(深入)”;
- 缓存未命中率下降,尤其是 L1 数据缓存 miss 明显减少;
- CAS 操作失败率降低,因为不再因无关字段更新而频繁失效本地缓存;
- 多核扩展性提升:4 核到 16 核的 QPS 不再反向衰减;
- 代价是内存占用上升,单个 @Contended 字段通常多占 128~256 字节。
替代方案与适用边界
@Contended 是最简洁的方案,但不是唯一选择:
-
手动填充:在字段前后声明 long 类型数组(如
long p1, p2, p3, p4, p5, p6, p7;),原理相同但侵入性强、易出错; -
内存对齐:通过
Unsafe.allocateMemory配合地址对齐控制,适用于自定义内存池等底层场景; - 结构拆分:将高频更新字段分散到不同对象中,利用 JVM 默认对象对齐(通常 8 字节)自然拉开距离,适合轻量优化。
真正需要 @Contended 的,是像 LongAdder、Disruptor 序列号、高性能计数器这类每微秒都要更新、且部署在多核服务器上的核心路径。


















