различное поведение java.lang.Object.wait () - PullRequest
2 голосов
/ 24 сентября 2019

Я пытался использовать метод java.lang.Object.wait() и написал 3 разных примера кода, в которых я получаю различное поведение метода wait().

образец 1)

class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadB b = new ThreadB();
        b.start();
        Thread.sleep(10000);
        synchronized (b) {
            System.out.println("main thread trying to call wait() method"); //--> 3
            b.wait();
            System.out.println("main thread got notification"); 
            System.out.println(b.total); 
        }
    }
}

class ThreadB extends Thread {
    int total = 0;
    public void run() {
        synchronized (this) {
            System.out.println("child thread starts calculation");  //--> 1
            for (int i=0; i<=100; i++) {
                total = total + i;
            }
            System.out.println("child thread trying to give notification"); //--> 2 
            this.notify();
        }
    }
}

образец2)

public class Main{
public static void main (String[] args) throws InterruptedException {
    Thread t = new Thread();    
    t.start();
    System.out.println("X"); //--> 1
    synchronized(t) { 
    System.out.println("starting to wait"); //--> 2
    t.wait(10000);
    System.out.println("waiting on t"); //--> 3
    }
    System.out.println("Y"); //--> 4
   }
}

образец 3)

public class Main{
public static void main (String[] args) throws InterruptedException {
     Thread t = new Thread() {public void run() 
                     {System.out.println("I am the second thread.");}};
     t.start();
     System.out.println("X"); //--> 1
     synchronized(t) { 
         Thread.sleep(4000);
         System.out.println("starting to wait"); //--> 2
         t.wait(10000);
         System.out.println("waiting on t"); //--> 3
     }
     System.out.println("Y"); //--> 4
  }
}

в образце 1)

основной поток переходит в состояние ожидания навсегда , так как он вызвал метод b.wait(), и нет потока для предоставления notify() или notifyAll() для объекта b.Был дочерний поток, который уже был прерван до того, как main поток вызвал метод b.wait().Этот вывод соответствует ожиданиям.

В примере 2)

основной поток переходит в состояние ожидания в течение 10 секунд (t.wait(10000);) после печати
X начинает ждать через 10 секунд основной поток выполняется ожидание t Y Это также мой ожидаемый результат.

В примере 3)

основной поток НЕ находится в состоянии ожидания (t.wait(10000);), хотянесомненно, что поток child был бы завершен к тому времени main поток вызвал t.wait(10000); Так почему он не ждал? и сразу выполнен начинает ждатьв ожиданииY Это НЕ мой ожидаемый результат.

1 Ответ

1 голос
/ 24 сентября 2019

Для первых двух примеров ваши ожидания кажутся правильными.В третьем примере кажется разумным ожидать, что t завершит работу до того, как основной поток начнет ждать, а затем основной поток зависнет до истечения времени ожидания.

Но, как вы заметили, это не то, что происходит.

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

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

Чтобы t уведомил основной поток, он должен был быть живым в то время, когда t начал ждать.Так что же его держит?

Существует не очень известное поведение, которое возникает, когда поток завершается.Документация API для Thread.join гласит:

В этой реализации используется цикл вызовов this.wait, обусловленный this.isAlive.Когда поток завершает работу, вызывается метод this.notifyAll.Рекомендуется, чтобы приложения не использовали wait, notify или notifyAll для экземпляров Thread.

Что происходит:

1) t печатает свой вывод и находится на выходе из своегометод run.

2) Между t и основным потоком существует гонка для захвата блокировки t.Это нужно для вызова notifyAll, главное нужно, чтобы войти в синхронизированный блок.Основной поток сначала захватывает блокировку.

3) t зависает до тех пор, пока не сможет захватить блокировку.

4) Основной поток входит в метод ожидания (снятие блокировки).

5) t получает блокировку и вызывает t.notifyAll.

5) Главный поток уведомляется и покидает метод ожидания (повторная блокировка).

Некоторые уроки:

  • Не синхронизировать по потокам (это хороший пример того, почему в документации API сказано не делать этого, здесь вы непреднамеренно задержали умирание потока своевременно).

  • Если поток не ожидает, он не получает уведомления.Если поток начинает ждать после того, как уведомление уже получено, это уведомление теряется.

  • Не полагайтесь исключительно на уведомления (это делает ваш код уязвимым для условий гонки), вместо этого используйте уведомлениянаряду с некоторым условием, которое может установить другой поток.Ожидание вызова в цикле с условием проверки.Если вы видите исходный код Thread.join, это хороший пример, он выглядит примерно так:

        while (isAlive()) {
            wait(0);
        }
    
  • Не спите, удерживая блокировку.Это делает систему менее отзывчивой без какой-либо выгоды.

  • Будьте очень осторожны, делая предположения о порядке вещей.

...