为什么在以下Java代码中:
public class WaitForOther {
private volatile boolean in = false;// need volatile for transitive closure
// to occur
public void stoppingViaMonitorWait() throws Exception {
final Object monitor = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (monitor) {
in = true;
try {
monitor.wait(); // this releases monitor
//Thread.sleep(8000); //No-op
System.out.println("Resumed in "
+ Thread.currentThread().getName());
} catch (InterruptedException ignore) {/**/
}
}
}
}).start();
System.out.println("Ready!");
while (!in); // spin lock / busy waiting
System.out.println("Set!");
synchronized (monitor) {
System.out.println("Go!");
monitor.notifyAll();
}
}
取消评论Thread.sleep(8000); //No-op
会导致缩短输出:
Ready!
Set!
Go!
否则会在中断的Thread-0中正确恢复:
Ready!
Set!
Go!
Resumed in Thread-0
这是JUnit测试,它调用上述行为,如评论中所要求的那样:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait() throws Exception {
cut.stoppingViaMonitorWait();
}
}
谢谢!
答案 0 :(得分:2)
我已经在JUnit中尝试过你的测试,但我与你的经历相反:
Thread.sleep
时,测试运行正常并打印“已恢复为<>”Thread.sleep
在代码中(实际执行)时,JVM终止,“Resumed in ...”不打印。原因是JUnit在测试完成后终止了VM。它做System.exit();
。你很幸运,你得到了案例1中的完整输出,因为它打印在一个单独的线程中,而JUnit没有等待那个线程。
如果要确保在测试方法结束之前完成线程,则需要让API等待线程,或者需要让测试等待线程。
如果您的stoppingViaMonitorWait
方法返回它创建的Thread,您可以等待测试。
@Test
public void testStoppingViaMonitorWait() throws Exception {
Thread x = cut.stoppingViaMonitorWait();
x.join();
}
另一个选择是你将一个线程池(一个ExecutorService
的实例)注入你正在测试的类中,让它在池上调度它的线程(在任何情况下都更好),并在你的测试方法,您可以调用ExecutorService.awaitTermination
。
答案 1 :(得分:1)
您的观察与Java内存模型无关。尝试运行以下代码:
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Will not be printed when running JUnit");
}
}.start();
如果你在main方法中运行它,那么线程 - 不是a deamon thread - 使进程保持活动状态,直到睡眠时间结束,这样就会打印最后一行,然后进程结束。
由于JUnit正在测试可能损坏的代码,因此需要假设从单元测试启动的线程可能永远不会结束。因此,框架将不会等到所有启动的线程都终止,而是在返回所有测试方法后显式终止Java进程。 (假设您没有为此次超时适用的测试设置超时。)这样,JUnit测试套件就不容易受到测试中破坏的代码的攻击。</ p>
但是,如果您的测试套件执行的时间比启动的线程的运行时间长,您仍然可以看到打印的语句。例如,通过添加另一个测试:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait1() throws Exception {
cut.stoppingViaMonitorWait();
}
@Test
public void testStoppingViaMonitorWait2() throws Exception {
Thread.sleep(9000);
}
}
你仍然可以看到印刷线。
但请注意,此打印结果相当不稳定,因为它依赖于特定的,非确定性的测试执行顺序和非等待代码的短运行时间。 (然而,它通常适用于运行这个示例,这对于演示目的来说是好的)。
答案 2 :(得分:0)
此代码滥用了Java的低级线程通信结构,如wait
和notify
。目前尚不清楚您希望使用此代码建立什么。
以下是我对您的程序行为的观察(这些与您的行为不同。我使用服务器和客户端编译器在IDE和命令行中运行它):
sleep()
// 已注释 //:Ready!
thread is daemon? : false
Set!
Go!
Resumed in Thread-0
sleep()
取消注释:Ready!
thread is daemon? : false
Set!
Go!
<< 8 seconds pass >>
Resumed in Thread-0
因此,它符合您的设计:main
线程启动非守护程序线程(比如thread-0
)。然后main
线程和thread-0
竞争锁定(monitor
),但thread-0
总是胜出,因为你想要它。
如果thread-0
抓取锁,它会在in
上设置易失性写入并立即放弃main
线程的锁定,以通过wait()
通知它。现在main
线程看到了易失性更新(可见性保证),突破忙等待,抓住锁定,打印&#34; Go&#34;并通知thread-0
,根据其睡眠状态得到通知。
由于忙碌的等待,main
线程赢得了锁定竞赛。
我添加了一行来澄清您的线程是否是守护程序,并且因为JVM没有退出,因为您的线程是非守护程序线程,所以它保持足够长的时间以便线程唤醒从睡眠中打印出它的线条。
有人说wait()
应该仅用于等待循环中的条件变量。