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'
}
}
答案 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才能保证看到真(即,根本没有保证)。
原因是,挥发性有助于建立先发生过的关系。它如下所示:在对同一变量的任何后续读取之前发生对易失性变量的写入。