我在一本书中遇到过这段代码。它指出 NoVisibility 可能会永远循环,因为ready的值可能永远不会变成 对读者线程可见。
我对这个说法感到困惑。为了让循环永远运行,
ready
必须始终为 false,这是默认值。这意味着它在执行 ready = true;
时一定会失败,因为读取器线程将始终从内存中读取 ready
变量。分配发生在 CPU 中,并且将数据刷新回主内存时一定存在一些问题。我想我需要一些关于它如何失败的情况的解释,或者我可能错过了其他部分。
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
总之,书上说的是正确的。
您假设Java 在这里会表现得直观。事实上,也可能不是。事实上,如果您不遵守规则,Java 语言规范允许非直观的行为。
更具体地说,在您的示例中,不能保证第二个线程会看到第一个线程分配给
ready
1的结果。这是由于以下原因:
ready
的值缓存在第一个或第二个线程的寄存器中。如果您希望保证第二个线程将看到写入的结果,则两个线程对
ready
的读取和写入必须(正确)同步,或者必须将 ready
变量声明为易失性。
所以...
这意味着它在执行时一定会失败
,因为读取器线程将始终从内存中读取就绪变量。ready = true;
...不正确。本示例中的“因为”不受 Java 语言规范的保证。
是的。这是不直观的。基于对单线程程序的理解来使用您的直觉是不可靠的。如果你想完全理解什么是保证的,什么是不保证的,你需要研究JLS第17.4节中“Java内存模型”的规范。警告:这不是一本容易阅读的书。
1 - 它可能会立即看到结果,或者在短暂或长时间的延迟后看到结果。或者它可能“永远不会”看到它们。而且,不同系统的行为以及 Java 平台的版本可能会有所不同。因此,(幸运的是)您在一个系统上一直运行的程序可能并不总是在另一个系统上运行。
private static volatile boolean ready;
易失性的作用是告诉你的程序从内存中准备好,而不是从堆栈中准备好。
实际上jvm所做的就是翻译:
while(flag){...}
致:
if(flag){
while(true){
}
堆栈是在线程创建时创建的。它收集变量的值以便稍后使用它们。
这是我的理解,如有错误请指正!