Java 中的线程间通信
Inter-thread communication in Java
为什么在下面的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!
否则会在中断的线程 0 中正确恢复:
Ready!
Set!
Go!
Resumed in Thread-0
这是调用上述行为的 JUnit 测试,正如评论中所要求的那样:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait() throws Exception {
cut.stoppingViaMonitorWait();
}
}
谢谢!
此代码滥用了 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
上设置volatile write并立即放弃锁让main
线程通过wait()
ing通知它在上面。现在 main
线程看到易失更新(visibility gua运行tee),跳出忙等待,获取锁,打印 "Go" 并通知 thread-0
,根据其睡眠状态得到通知。
main
线程由于忙等待而无法赢得锁。
我添加了一行来说明你的线程是否是 daemon 并且由于 JVM 没有退出,因为你的线程是一个非守护线程,它保持足够长的时间从睡眠中唤醒并打印其行的线程。
据说 wait()
应该只用于等待 在循环中 条件变量。
我已经在 JUnit 中尝试过你的测试,但我得到的结果与你的经历相反:
- 当
Thread.sleep
被注释掉时,测试运行正常并打印 "Resumed in <>"
- 当
Thread.sleep
在代码中(实际执行)时,JVM 终止并且 "Resumed in ..." 不 打印。
原因是 JUnit 在测试完成后终止了 VM。它做了 System.exit();
。您很幸运在案例 1 中获得了完整的输出,因为它是在单独的线程中打印的,而 JUnit 并不等待该线程。
如果你想确保线程在测试方法结束之前完成,要么你需要让你的 API 等待线程,要么你需要让测试等待线程。
如果你的stoppingViaMonitorWait
方法returns它创建的线程,你可以在测试中等待。
@Test
public void testStoppingViaMonitorWait() throws Exception {
Thread x = cut.stoppingViaMonitorWait();
x.join();
}
另一种选择是将线程池(ExecutorService
的一个实例)注入到您正在测试的 class 中,让它在池中安排线程(这在任何情况下都更好) case), 在你的测试方法中你可以调用 ExecutorService.awaitTermination
.
您的观察与 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 测试套件就不容易受到测试中损坏代码的影响。
但是,如果您的测试套件执行时间比启动线程的 运行时间长,您仍然可以看到打印的语句。例如通过添加另一个测试:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait1() throws Exception {
cut.stoppingViaMonitorWait();
}
@Test
public void testStoppingViaMonitorWait2() throws Exception {
Thread.sleep(9000);
}
}
您仍然可以看到打印的行。
但是请注意,此打印结果相当不稳定,因为它依赖于特定的、不确定的测试执行顺序和非等待代码的短 运行 时间。 (然而,它通常适用于 运行 这个示例,足以用于演示目的)。
为什么在下面的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!
否则会在中断的线程 0 中正确恢复:
Ready!
Set!
Go!
Resumed in Thread-0
这是调用上述行为的 JUnit 测试,正如评论中所要求的那样:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait() throws Exception {
cut.stoppingViaMonitorWait();
}
}
谢谢!
此代码滥用了 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
上设置volatile write并立即放弃锁让main
线程通过wait()
ing通知它在上面。现在 main
线程看到易失更新(visibility gua运行tee),跳出忙等待,获取锁,打印 "Go" 并通知 thread-0
,根据其睡眠状态得到通知。
main
线程由于忙等待而无法赢得锁。
我添加了一行来说明你的线程是否是 daemon 并且由于 JVM 没有退出,因为你的线程是一个非守护线程,它保持足够长的时间从睡眠中唤醒并打印其行的线程。
据说 wait()
应该只用于等待 在循环中 条件变量。
我已经在 JUnit 中尝试过你的测试,但我得到的结果与你的经历相反:
- 当
Thread.sleep
被注释掉时,测试运行正常并打印 "Resumed in <>" - 当
Thread.sleep
在代码中(实际执行)时,JVM 终止并且 "Resumed in ..." 不 打印。
原因是 JUnit 在测试完成后终止了 VM。它做了 System.exit();
。您很幸运在案例 1 中获得了完整的输出,因为它是在单独的线程中打印的,而 JUnit 并不等待该线程。
如果你想确保线程在测试方法结束之前完成,要么你需要让你的 API 等待线程,要么你需要让测试等待线程。
如果你的stoppingViaMonitorWait
方法returns它创建的线程,你可以在测试中等待。
@Test
public void testStoppingViaMonitorWait() throws Exception {
Thread x = cut.stoppingViaMonitorWait();
x.join();
}
另一种选择是将线程池(ExecutorService
的一个实例)注入到您正在测试的 class 中,让它在池中安排线程(这在任何情况下都更好) case), 在你的测试方法中你可以调用 ExecutorService.awaitTermination
.
您的观察与 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 测试套件就不容易受到测试中损坏代码的影响。
但是,如果您的测试套件执行时间比启动线程的 运行时间长,您仍然可以看到打印的语句。例如通过添加另一个测试:
public class WaitForOtherTest {
WaitForOther cut = new WaitForOther();
@Test
public void testStoppingViaMonitorWait1() throws Exception {
cut.stoppingViaMonitorWait();
}
@Test
public void testStoppingViaMonitorWait2() throws Exception {
Thread.sleep(9000);
}
}
您仍然可以看到打印的行。
但是请注意,此打印结果相当不稳定,因为它依赖于特定的、不确定的测试执行顺序和非等待代码的短 运行 时间。 (然而,它通常适用于 运行 这个示例,足以用于演示目的)。