对于我正在写的最新库,我写了一个无限循环的线程。在此循环中,我从条件语句开始检查线程对象上的属性。但是,无论该属性具有什么初始值,它都将是即使更新后仍会返回的值。
除非,我会进行一些打扰,例如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
的打印方法,但某些简单的分配代码无效。我认为这是因为在编译时会将其作为未使用的代码拉出。
所以,我的问题确实是:为什么我必须使用某种中断来使线程正确检查条件?
答案 0 :(得分:3)
所以,我的问题确实是:为什么我必须使用某种中断来使线程正确检查条件?
好吧,你不必。至少有两种方法可以在不使用“中断”的情况下实现此特定示例。
flag
为volatile
,那么它将起作用。如果您声明flag
为private
,编写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());
}
但是您为什么需要这样做?
因为除非您使用volatile
或synchronized
(并且您正确使用了synchronized
),否则不能保证一个线程看到另一个线程所做的内存更改。
在您的示例中,子线程看不到flag
的最新值。 (并不是条件本身不正确或“不起作用”。它们实际上是在获取陈旧的输入。这是“垃圾进,垃圾出”。)
Java语言规范精确地规定了保证一个线程能够看到(先前的)另一线程进行的写操作的条件。规范的这一部分称为Java内存模型,位于JLS 17.4中。 Brian Goetz等人在 Java Concurrency in Practice 中有一个更容易理解的解释。
请注意,意外行为可能是由于JIT决定将标志保留在寄存器中。也可能是JIT编译器已决定不需要强制内存高速缓存直写等。 (JIT编译器不想在对每个字段的每次内存写入中都强制执行直写操作。这将对多核系统造成重大影响……大多数现代机器都是这样。)
Java中断机制是解决此问题的另一种方法。您不需要任何同步,因为该方法会调用该同步。另外,当您要中断的线程当前正在等待或在可中断的操作中阻塞时,中断将起作用。例如在Object::wait
通话中。
答案 1 :(得分:2)
由于未在该线程中修改变量,因此JVM可以自由有效地优化检查。要强制进行实际检查,请使用volatile
关键字:
public volatile boolean flag = false;