简述伪共享的概念以及如何避免
                           
天天向上
发布: 2025-07-13 11:54:51

原创
819 人浏览过

伪共享(False Sharing)是多线程并发编程中的一种性能隐患,常出现在使用多核 CPU 进行共享内存访问时。虽然不会导致程序错误,但会严重影响性能,尤其在高频写操作下。


一、什么是伪共享(False Sharing)?

定义:

多个线程访问不同变量,但这些变量共享了同一个缓存行(cache line),会导致频繁的缓存同步,产生性能瓶颈。

通俗理解:

  • CPU 缓存是以 缓存行(cache line) 为单位读写的(通常为 64 字节)。
  • 如果多个线程访问的不同变量恰好落在同一个 cache line 上:
  • 即使操作不同变量,仍会导致缓存失效(cache invalidation)。
  • 多个 CPU 频繁地进行缓存一致性同步(MESI 协议),导致性能下降。

二、为什么会有伪共享问题?

Java 中一个对象包含多个字段时,它们可能紧密排列在内存中,甚至多个对象也可能在数组里紧邻排布。

当不同线程修改这些字段(即便不是同一个字段),只要它们落在同一缓存行,就会引发伪共享问题。


三、伪共享示例(Java)

public class FalseSharingDemo {
    static class Data {
        public volatile long value = 0L;
    }

    static final int NUM_THREADS = 4;
    static final long ITERATIONS = 10_000_000L;
    static final Data[] datas = new Data[NUM_THREADS];

    static {
        for (int i = 0; i < NUM_THREADS; i++) {
            datas[i] = new Data();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[NUM_THREADS];
        for (int i = 0; i < NUM_THREADS; i++) {
            final int index = i;
            threads[i] = new Thread(() -> {
                for (long j = 0; j < ITERATIONS; j++) {
                    datas[index].value = j;
                }
            });
        }

        long start = System.nanoTime();
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        long end = System.nanoTime();

        System.out.println("Duration: " + (end - start) / 1_000_000 + " ms");
    }
}

🔺 问题:
所有 value 字段可能被分配在同一个缓存行中,多线程同时写入不同元素,依然会互相干扰。


四、如何避免伪共享?

方法 1:使用缓存行填充(padding)

手动添加无用字段,使真正要用的字段不共享同一 cache line。

class PaddedData {
    public volatile long value = 0L;
    // 添加填充字段,避免伪共享
    public long p1, p2, p3, p4, p5, p6, p7;
}

每个字段占 8 字节,添加 7 个即可填满 64 字节缓存行。


方法 2:使用 JDK 8 的 @Contended 注解(推荐方式)

JDK 8+ 提供了一个专门用于避免伪共享的注解:

import jdk.internal.vm.annotation.Contended;

public class ContendedData {
    @Contended
    public volatile long value;
}

📌 需要 JVM 参数开启:

-XX:-RestrictContended

方法 3:使用数组分配间隔空间(不推荐)

人为在数组中加间隔项,比如:

datas[i * 16] = new Data();

让实际访问项距离足够远,但这种方式依赖具体实现,不可靠


方法 4:使用高性能并发类(如 LongAdder)

JDK 提供了 LongAdder 等专门优化的类,它们内部使用分段 + padding技术避免伪共享。

适用于高并发场景下频繁加法统计:

LongAdder adder = new LongAdder();
adder.increment(); // 比 AtomicLong 性能高

五、伪共享 vs 真共享

类型说明
真共享多线程同时访问同一个变量
伪共享多线程访问不同变量,但在同一 cache line 内
无共享多线程各自独立访问完全不同的 cache line 数据

六、参考资料与官方出处


七、总结(面试 & 实战)

问题简洁回答
什么是伪共享?多线程访问不同变量,但变量共享同一缓存行,导致性能下降
为什么会慢?缓存一致性协议导致频繁无效同步(MESI 协议)
怎么避免?使用 padding、@Contended、或高并发类如 LongAdder
实际影响?在高并发写操作中,性能可能下降 5~30 倍

更多详细内容请关注其他相关文章!

发表回复 0

Your email address will not be published. Required fields are marked *