Почему в следующем коде 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();
    }



}

Благодарность!

1
Simeon Leyzerzon 5 Сен 2016 в 04:17

2 ответа

Ваше наблюдение не связано с моделью памяти 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();

Если вы запустите его в основном методе, поток, не являющийся потоком демона, будет поддерживать процесс в рабочем состоянии до периода ожидания. закончился так, что печатается последняя строка, после чего процесс завершается.

Поскольку 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);
  }
}

Вы все еще можете видеть напечатанную строку.

Однако обратите внимание, что этот результат печати является довольно нестабильным, поскольку он зависит от определенного, недетерминированного порядка выполнения теста и короткого времени выполнения для не ожидающего кода. (Тем не менее, это обычно работает для запуска этого примера, что достаточно хорошо для демонстрационных целей).

1
Community 23 Май 2017 в 10:33

Этот код неправильно использует такие низкоуровневые конструкции взаимодействия потоков 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 видит изменчивое обновление (гарантия видимости), выходит из режима ожидания-занятости, захватывает блокировку, печатает "Go" и уведомляет thread-0, который, в зависимости от его состояния сна, получает уведомление.

Поток main не выиграет гонку за блокировку из-за ожидания занятости.

Я добавил строку, чтобы уточнить, является ли ваш поток демоном или нет, и поскольку JVM не завершается, потому что ваш поток не является потоком демона, он остается достаточно долго, чтобы поток проснулся из сна и печатает свою строку.

Я добавил строку, чтобы уточнить, является ли ваш поток демоном или нет, и поскольку JVM не завершается, потому что ваш поток не является потоком демона, он остается достаточно долго, чтобы поток проснулся из сна и печатает свою строку.…

0
Kedar Mhaswade 5 Сен 2016 в 22:33