java 实例变量对其他线程不可见

java instance variable not visible to other threads

我在一本书中遇到过这段代码。它声明 NoVisibility 可能永远循环,因为 ready 的值可能永远不会变为 reader 线程可见。

我对这种说法感到困惑。为了使循环永远 运行,ready 必须始终为 false,这是默认值。这意味着它必须在执行 ready = true; 时失败,因为 reader 线程将始终从内存中读取 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;
     }
 }

ready 的值可能会更新,但其他线程可能永远不知道。你需要可变变量!一个线程假定该变量仅由该线程且唯一的线程使用。因此,它从它创建的堆栈中读取它的值。

private static volatile boolean ready;

volatile 的作用是告诉您的程序从内存中准备好,而不是从堆栈中准备好。

实际上 jvm 所做的就是翻译:

while(flag){...}

收件人:

if(flag){
    while(true){
}

创建线程时创建堆栈。它收集变量的值以便以后使用它们。

这是我的理解,如有错误请指正!

你的理解有问题。您 假设 Java 在这里的行为很直观。事实上,它可能不会。而且,事实上,如果您不遵守规则,Java 语言规范 允许 非直觉行为。

更具体地说,在您的示例中,不保证第二个线程将看到第一个线程分配给ready[=36=的结果]1。这是由于以下原因:

  1. 编译器将 ready 的值缓存在第一个或第二个线程的寄存器中。
  2. 编译器不包括强制将写入从一个内核的内存缓存刷新到主内存或类似内容的指令。

如果你想保证第二个线程将看到写入的结果,那么两个线程对 ready 的读写必须(正确地)同步,或者 ready变量必须声明为 volatile。

所以...

This means it must fail at executing ready = true; because the reader thread will always read the ready variable from memory.

不正确。此示例中的 Java 语言规范不保证“因为”。

是的。这是不直观的。依靠你对单线程程序的理解的直觉是不可靠的。如果您想了解什么是保证和什么不是保证,请研究 JLS 的第 17.4 节中的“Java 内存模型”规范。

总之,书是对的。


1 - 它可能会立即看到结果,或者在短暂或长时间的延迟之后。或者它可能 永远不会 看到它们。并且行为可能因一个系统和下一个系统以及 Java 平台的版本而异。因此,您的程序(幸运地)一直在一个系统上运行可能并不总是在另一个系统上运行。