返回

深入剖析:线程安全性之可见性、缓存一致性(MESI)以及伪共享问题

后端

一、可见性问题

可见性问题是指线程A对一个共享变量所做的修改,对线程B不可见。这可能是由于缓存一致性问题造成的,也可能是由于编译器优化导致的。

1. 代码示例

以下代码演示了可见性问题:

public class VisibilityDemo {

    private static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!stop) {
                // do something
            }
        });
        thread.start();

        // 主线程等待一段时间,然后修改stop变量
        Thread.sleep(1000);
        stop = true;

        // 主线程等待子线程结束
        thread.join();
    }
}

然后惊奇的发现,程序并没有停止呀,可见性问题就此展开!

2. 活性

活性(liveness)是指一个线程最终能够执行完成。可见性问题可能导致线程陷入死循环,无法执行完成,从而导致活性问题。

二、缓存一致性协议MESI

MESI协议(修改、独占、共享、无效)是一种缓存一致性协议,它用于保证多核处理器中多个处理器缓存的数据一致性。

1. 协议状态

MESI协议定义了四种缓存状态:

  • 修改(Modified):该缓存行已被修改,并且只有该缓存拥有该缓存行的副本。
  • 独占(Exclusive):该缓存行已被加载到缓存中,但尚未被修改。
  • 共享(Shared):该缓存行已被加载到多个缓存中,并且这些缓存中的数据副本都是一致的。
  • 无效(Invalid):该缓存行在该缓存中无效,因此不能被使用。

2. 协议操作

当一个处理器需要访问一个缓存行时,它首先会检查该缓存行是否在自己的缓存中。如果在,则直接使用该缓存行。如果不在,则需要从内存中加载该缓存行。

当一个处理器修改了一个缓存行时,它会将该缓存行的状态标记为修改。当其他处理器需要访问该缓存行时,它们会发现该缓存行的状态为修改,因此需要从内存中加载该缓存行。

三、伪共享问题

伪共享问题是指多个处理器同时访问同一个缓存行,导致缓存一致性问题。这可能发生在以下两种情况下:

  • 两个或多个处理器同时访问同一个变量。
  • 两个或多个处理器同时访问相邻的变量。

伪共享问题可能导致性能下降,因为处理器需要频繁地从内存中加载缓存行。

四、解决办法

可见性问题可以通过使用volatile或原子操作来解决。缓存一致性问题可以通过使用MESI协议来解决。伪共享问题可以通过使用缓存对齐或使用锁来解决。

五、总结

线程安全和缓存一致性是多线程编程中两个非常重要的概念。理解和掌握这些概念对于编写正确的多线程程序至关重要。