Java中的线程间通信

时间:2016-09-05 01:17:12

标签: java multithreading junit java-memory-model

为什么在以下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();
    }



}

谢谢!

3 个答案:

答案 0 :(得分:2)

我已经在JUnit中尝试过你的测试,但我与你的经历相反:

  1. 当评论Thread.sleep时,测试运行正常并打印“已恢复为<>”
  2. Thread.sleep在代码中(实际执行)时,JVM终止,“Resumed in ...”打印。
  3. 原因是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的低级线程通信结构,如waitnotify。目前尚不清楚您希望使用此代码建立什么。

以下是我对您的程序行为的观察(这些与您的行为不同。我使用服务器和客户端编译器在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()应该仅用于等待循环中的条件变量。