2012-01-27 14 views
6

Napisałem aplikację testową, która nigdy nie powinna się zatrzymać. Wydaje obiekt t.wait() (t jest obiektem Thread), ale nigdy nie wywołuję powiadomień. Dlaczego ten kod się kończy? Pomimo synchronizacji głównego wątku na t, wątek z wątku jest uruchamiany, więc nie blokuje tego obiektu.Czy Java niejawnie powiadamia o oczekujących wątkach?

public class ThreadWait { 
    public static void main(String sArgs[]) throws InterruptedException { 
     System.out.println("hello"); 
     Thread t = new MyThread(); 
     synchronized (t) { 
      t.start(); 
      Thread.sleep(5000); 
      t.wait(); 
      java.lang.System.out.println("main done"); 
     } 
    } 
} 

class MyThread extends Thread { 
    public void run() { 
     for (int i = 1; i <= 5; i++) { 
      java.lang.System.out.println("" + i); 
      try { 
       Thread.sleep(500); 
      } catch (Exception e) { 
       throw new RuntimeException(e); 
      } 
     } 
    } 
} 

Powoduje to, że wątek główny czeka 5 sekund i podczas tego czasu pracownik daje wynik. Następnie po 5 sekundach kończy się program. t.wait() nie czeka. Jeśli główny wątek nie przespałby się przez 5 sekund (komentując tę ​​linię), wówczas t.wait() faktycznie czekałby aż robot zakończy pracę. Oczywiście, tutaj jest metoda join(), ale nieoczekiwanie, wait() robi to samo co join(). Czemu?

Może JVM widzi, że ponieważ działa tylko jeden wątek, nie ma możliwości powiadomienia głównego wątku i rozwiązania zakleszczenia. Jeśli to prawda, czy jest to udokumentowana funkcja?

Mam testowanie w systemie Windows XP, Java 6.

+0

Co dzieje się, gdy śpisz tylko przez 1 lub 2 sekundy? Założę się, że główny wątek będzie czekał na zakończenie wątku t ... – Renato

Odpowiedz

9

Czekasz na Thread - i choć większość obiektów nie są niejawnie powiadomiony, o Thread obiekt zostanie powiadomiony, gdy wątek kończy. Jest to gdzieś udokumentowane (szukam tego ...), że powinieneś nie użyć obiektu wait/notify na obiektach Thread, ponieważ odbywa się to wewnętrznie.

Jest to dobry przykład, dlaczego najlepiej jest używać obiektu "prywatnego" do synchronizacji (i czekać/powiadamiania) - czegoś, o czym wie twój kod. Zwykle używam coś takiego:.

private final Object lock = new Object(); 

(Na ogół jednak jest to środek czyszczący do korzystania z niektórych abstrakcji wyższego poziomu świadczonych przez java.util.concurrent jeśli potrafisz Jak zauważył w komentarzach, to również dobry pomysł wdrożenia Runnable zamiast samodzielnego przedłużania Thread).

+0

Liczba metod w wątku jest zsynchronizowana z tym obiektem i użyj wait/notify, więc należy się spodziewać fałszywych przebudzeń. Jest to również dobra praktyka, aby nie rozszerzać Nici, ale używać Runnable, które jest owinięte Nicią. Pozwoli to również uniknąć tego problemu. –

+1

@PeterLawrey: Tak długo, jak OP używał czekania/powiadamiania na runnable, nie na wątku :) Wciąż wolę mieć "głupi" obiekt, którego nie wystawiam * nigdzie * jeszcze, osobiście. –

+0

Najlepiej, jeśli chcesz mieć obie, ponieważ nie wiesz, jak kod mógł zostać zmieniony w przyszłości. IMHO o wielu strategiach, które są najlepszą praktyką, lepiej chroni twój kod. –

8

The JavaDoc dla wait daje odpowiedź: fałszywych wybudzeń są możliwe. Oznacza to, że JVM może zakończyć połączenie z numerem wait, kiedy tylko chce.

Dokumentacja daje nawet rozwiązanie, jeśli nie chcesz tego (co prawdopodobnie zawsze ma miejsce): włóż połączenie do wait w pętli i sprawdź, czy warunek, na który czekasz, stał się prawdziwy po każdym wznowieniu.

+3

@Boris W przypadku fałszywego przebudzenia nie ma wyjątków. Wyjątek "InterruptedException" jest zgłaszany tylko wtedy, gdy wątek zostanie przerwany. –

+2

Wątpię, czy to * faktycznie * jest fałszywym przebudzeniem - uważam, że jest to szczególnie zachowanie oczekiwania na "Nici"; * zostanie * powiadomiony, gdy wątek się zakończy. To nie jest fałszywe w normalnym sensie. –

+2

Nawet bez fałszywych wyborów, ze wzorem monitora (lub dowolnego z jego wariantów) zawsze chcesz zapętlić i sprawdzić warunki. To zapobiega problemom, jeśli później dodajesz inny warunek (java i monitor C# używają jednego obiektu jako zmiennej blokującej i warunku, więc nie możesz użyć jednej zmiennej warunkowej dla warunku). Zapobiega to także procedurze, jeśli warunek został spełniony, ale inny wątek oczekujący na ten sam obiekt poszedł pierwszy, a teraz warunek nie jest już prawdziwy. –

Powiązane problemy