多线程为什么Java中的空while在其他线程设置条件时不会中断?
在尝试对线程类进行单元测试时,我决定使用活动等待来控制被测试类的行为。为此使用空while语句未能达到我的目的。所以我的问题是:
为什么第一个代码没有完成,而第二个代码完成了
有一个similar question,但它没有真正的答案,也没有MCVE,而且更具体
未完成:
public class ThreadWhileTesting {
private static boolean wait = true;
private static final Runnable runnable = () -> {
try {Thread.sleep(50);} catch (InterruptedException ignored) {}
wait = false;
};
public static void main(String[] args) {
wait = true;
new Thread(runnable).start();
while (wait); // THIS LINE IS IMPORTANT
}
}
是否完成:
public class ThreadWhileTesting {
private static boolean wait = true;
private static final Runnable runnable = () -> {
try {Thread.sleep(50);} catch (InterruptedException ignored) {}
wait = false;
};
public static void main(String[] args) {
wait = true;
new Thread(runnable).start();
while (wait) {
System.out.println(wait); // THIS LINE IS IMPORTANT
}
}
}
我怀疑空while会被Java编译器优化,但我不确定。如果这种行为是有意的,我如何才能实现我想要的?(是的,由于我无法将locks用于此测试,因此打算进行活动等待。)
# 1 楼答案
简而言之,这些示例中的和都是不正确的,但第二个示例之所以有效,是因为
System.out
流的实现工件更深入的解释是,根据JLS记忆模型,这两个例子有许多法律执行痕迹,这些痕迹会给(你)带来意想不到的行为。JLS是这样解释的(JLS 17.4):
在第一个示例中,一个线程更新变量,另一个线程更新变量,而tro线程之间没有任何形式的同步。简而言之,这意味着JLS不能保证写入线程进行的内存更新对读取线程都是可见的。实际上,我上面引用的JLS文本意味着编译器有权假设变量从未更改。如果使用JLS 17.4中规定的规则执行分析,则读取线程从未看到更改的执行跟踪是合法的
在第二个示例中,
println()
调用(可能)会导致内存缓存意外刷新。结果是,您得到了一个不同的(但同样合法的)执行跟踪,代码“工作”使两个示例都起作用的简单修复方法是将
wait
标志声明为volatile
。这意味着在一个线程中写入变量和在另一个线程中后续读取之间存在之前发生的关系。这反过来意味着,在所有合法执行跟踪中,readin线程都可以看到写入的结果这是JLS实际表述的一个大大简化的版本。如果你真的想了解技术细节,它们都在规范中。但是要做好准备,努力了解细节
# 2 楼答案
wait
不是易变的,循环体是空的,因此线程没有理由相信它会改变。这是准时到的如果
wait
最初为真,则永远不会完成简单的解决方案就是使},这会阻止JIT进行这种优化
wait
{至于第二个版本的工作原理:
System.out.println
是内部同步的;如JSR133 FAQ中所述:因此
wait
变量将在下一次循环中从主内存中重新读取然而,实际上并不能保证另一个线程中
wait
变量的写入被提交到主存;因此,正如@assylias在上面指出的,它可能不会在所有条件下都起作用。(使变量volatile
也可以解决这个问题)