java - Java Thread 似乎跳过了条件语句
问题描述
对于我最近写的一个库,我写了一个无限循环的线程。在这个循环中,我从一个检查线程对象属性的条件语句开始。然而,似乎无论属性具有什么初始值,即使在更新后也将返回。
除非我做某种中断,例如Thread.sleep
打印语句。
不幸的是,我不确定如何提出这个问题。否则我会查看 Java 文档。我已将代码简化为一个简单的示例,以简单的方式解释问题。
public class App {
public static void main(String[] args) {
App app = new App();
}
class Test implements Runnable {
public boolean flag = false;
public void run() {
while(true) {
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {}
if (this.flag) {
System.out.println("True");
}
}
}
}
public App() {
Test t = new Test();
Thread thread = new Thread(t);
System.out.println("Starting thread");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
t.flag = true;
System.out.println("New flag value: " + t.flag);
}
}
现在,我假设在我们更改flag
正在运行的线程上的属性值后,我们会立即看到大量的“True”向终端吐出。然而,我们不..
如果我取消注释Thread.sleep
线程循环内的行,程序将按预期工作,并且在我们更改App
对象中的值后,我们会看到打印出许多“True”行。另外,任何代替 的打印方法Thread.sleep
也可以,但一些简单的赋值代码却不行。我认为这是因为它在编译时作为未使用的代码被提取出来。
所以,我的问题真的是:为什么我必须使用某种中断来让线程正确检查条件?
解决方案
所以,我的问题真的是:为什么我必须使用某种中断来让线程正确检查条件?
好吧,你不必。至少有两种方法可以在不使用“中断”的情况下实现这个特定示例。
- 如果您声明
flag
为volatile
,那么它将起作用。 如果您声明
flag
为 beprivate
,编写synchronized
getter 和 setter 方法并将它们用于所有访问,它也将起作用。public class App { public static void main(String[] args) { App app = new App(); } class Test implements Runnable { private boolean flag = false; public synchronized boolean getFlag() { return this.flag; } public synchronized void setFlag(boolean flag) { return this.flag = flag; } public void run() { while(true) { if (this.getFlag()) { // Must use the getter here too! System.out.println("True"); } } } } public App() { Test t = new Test(); Thread thread = new Thread(t); System.out.println("Starting thread"); thread.start(); try { Thread.sleep(1000); } catch (InterruptedException e) {} t.setFlag(true); System.out.println("New flag value: " + t.getFlag()); }
但是为什么你需要这样做呢?
因为除非您使用 avolatile
或synchronized
(并且您使用synchronized
正确),否则不能保证一个线程看到另一个线程所做的内存更改。
在您的示例中,子线程看不到 的最新值flag
。(这并不是说条件本身不正确或“不起作用”。他们实际上得到了陈旧的输入。这是“垃圾输入,垃圾输出”。)
Java 语言规范精确地规定了保证一个线程可以看到(以前的)另一个线程进行的写入的条件。规范的这一部分称为 Java 内存模型,它位于JLS 17.4中。Brian Goetz 等人在Java Concurrency in Practice中有一个更容易理解的解释。
请注意,意外行为可能是由于 JIT 决定将标志保留在寄存器中。也可能是 JIT 编译器决定它不需要强制内存缓存写入等。(JIT 编译器不希望对每个字段的每次内存写入都强制进行直写。这将对多核系统造成重大的性能影响……大多数现代机器都是如此。)
Java 中断机制是处理这个问题的另一种方法。您不需要任何同步,因为该方法会调用它。此外,当您尝试中断的线程当前正在等待或阻塞可中断操作时,中断将起作用;例如在Object::wait
通话中。
推荐阅读
- android - 下载 Appium 时出错
- php - 使用 curl 调用获取动态网页的源代码
- django - 在 Django 中加入 2 个模型
- javascript - Â 生产服务器上 SVG 中的字符(jointjs 图)
- php - Laravel 找不到 css 文件:GET http://localhost:8000/cssFile net::ERR_ABORTED
- c# - 局域网内的 Winforms 和 SQL 应用程序
- ansible - 从 Ansible 中的主机名获取数字
- php - Symfony - 复选框形式
- maven - 我在管理中心看不到页面和其他资源
- android - 如何使用 RRO 框架调整系统应用自定义颜色?