高速缓存一致性在无限循环演示中不起作用

时间:2017-03-13 13:59:12

标签: java multithreading caching cpu-cache

this question中有这样一句话:

  

所有缓存都是连贯的。也就是说,对于相同的内存位置,您永远不会有两个不同的值。这种一致性由MESI协议版本维护

我的问题是为什么iv.stop在下面的代码块中总是false,似乎缓存一致性没有生效。顺便说一句,我的电脑的CPU是i7-4700HQ。

我当然知道,在读取操作和共享变量stop的写入操作之间没有发生之前的关系,这是Java中的数据竞争。我只是想知道为什么缓存一致性没有生效。因为线程t2已经在其运行的核心中更改了stop的缓存,所以核心的缓存似乎应该根据缓存一致性在线程t1运行时看到此更改。

public class InfiniteLoop {
    boolean stop = false;

    Boolean another = null;

    public static void main(String[] args) {

        final InfiniteLoop iv = new InfiniteLoop();

        Thread t1 = new Thread(() -> {
            System.out.println("t1 iv address-->"+iv); //t1 iv address-->com.nestvision.thread.InfiniteLoop@48b96cb8
            while (!iv.stop) {
                //Not System.out.println(iv.stop) here to avoid 
                //a lock action of PrintStream object.
                iv.another = iv.stop;
            }
            System.out.println("done");
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 iv address-->"+iv);//t2 iv address-->com.nestvision.thread.InfiniteLoop@48b96cb8
            iv.stop = true;
            System.out.println("t2 end");
        });
        t2.start();
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(iv.another); //this will print 'false'
    }
}

1 个答案:

答案 0 :(得分:2)

首先,您的打印结尾将在线程开始之前打印,并且肯定在它们完成之前打印。您可能希望使用一些java并发类来阻塞主线程,直到其他线程完成 虽然这可能会无限循环并且永远等待,直到您执行下面的修复。

另一个问题是您的stop变量不是volatile,因此不同的主题可能看不到该值。

另一个答案包含有关跨处理器How is memory inconsistency different from thread interleaving?

的布尔值和L1 / L2缓存的信息

相关引用:

  

线程A读取布尔值:它必须读为false(参见前面的项目符号点)。当这种读取发生时,可能会发生某些内存页面(包括包含该布尔值的内存页面)将被缓存到Core 1的L1缓存或L2缓存中 - 该特定内核本地的任何缓存。

     

线程A否定并存储布尔值:它现在将存储为true。但问题是:在哪里?在发生之前发生之前,线程A可以自由地将此新值存储在运行该线程的Core的本地缓存中。因此,该值可能会在Core 1的L1 / L2缓存上更新,但在处理器的L3缓存或RAM中保持不变。

     

经过一段时间(根据您的挂钟),线程B读取布尔值:如果线程A没有将更改刷新到L3或RAM,则完全有可能线程B将读取错误。另一方面,如果线程A刷新了更改,则线程B可能会读取为真(但仍然无法保证 - 线程B可能已收到线程M的内存视图副本,并且由于缺少发生之前,它不会再次进入RAM并仍然会看到原始值。

     

保证任何事情的唯一方法是在之前有一个明确的发生:它会强制线程A刷新其内存,并强制线程B不从本地缓存读取,而是真正从“权威”读取它源。

     

如果没有事先发生,正如您在上面的示例中所看到的那样,无论在不同线程中的事件之间经过多长时间(从您的角度来看),任何事情都可能发生。

     

...

     

如果该布尔变量被标记为volatile ...那么,只有这样,线程B才能保证看到真(即,根本没有保证)。

     

原因是,挥发性有助于建立先发生过的关系。它如下所示:在对同一变量的任何后续读取之前发生对易失性变量的写入。