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

问题描述 投票:0回答:2

我在一本书中遇到过这段代码。它指出 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 multithreading concurrency thread-safety java-threads
2个回答
4
投票

总之,书上说的是正确的。

假设Java 在这里会表现得直观。事实上,也可能不是。事实上,如果您不遵守规则,Java 语言规范允许非直观的行为。

更具体地说,在您的示例中,不能保证第二个线程会看到第一个线程分配给

ready
1的结果。这是由于以下原因:

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

如果您希望保证第二个线程将看到写入的结果,则两个线程对

ready
的读取和写入必须(正确)同步,或者必须将
ready
变量声明为易失性。

所以...

这意味着它在执行时一定会失败

ready = true;
,因为读取器线程将始终从内存中读取就绪变量。

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

是的。这是不直观的。基于对单线程程序的理解来使用您的直觉是不可靠的。如果你想完全理解什么是保证的,什么是不保证的,你需要研究JLS第17.4节中“Java内存模型”的规范。警告:这不是一本容易阅读的书。


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


1
投票

private static volatile boolean ready;

易失性的作用是告诉你的程序从内存中准备好,而不是从堆栈中准备好。

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

while(flag){...}

致:

if(flag){ while(true){ }

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

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

© www.soinside.com 2019 - 2024. All rights reserved.