2011-12-19 11 views
12

Mam wątek, który aktualizuje jego stan od czasu do czasu i chcę, aby drugi wątek mógł poczekać na pierwszy wątek do zrobienia. Coś takiego:Oczekiwanie na wydarzenie w Javie - jak trudne jest to?

Thread 1: 
    while(true) { 
     ...do something... 
     foo.notifyAll() 
     ...wait for some condition that might never happen... 
     ... 
    } 

Thread 2: 
    ... 
    foo.wait(); 
    ... 

Teraz to wygląda ładnie i wszystko chyba gwintu 1 za notifyAll() biegnie przed gwintu 2 na wait(), w którym to przypadku gwintu 2 czeka aż gwintu 1 poinformuje podmiot ponownie (która nigdy nie może się zdarzyć) .

Moi możliwe rozwiązania:

a) mogę użyć CountDownLatch lub przyszłości, ale oba mają ten problem, że właściwie tylko uruchomić raz. Oznacza to, że w pętli while 1 wątku, musiałbym utworzyć nowe foo, aby czekać za każdym razem, a wątek 2 musiałby zapytać, na które foo czekać. Mam złe przeczucia prostu pisząc

while(true) { 
    foo = new FutureTask(); 
    ... 
    foo.set(...); 
    ...wait for a condition that might never be set... 
    ... 
} 

a obawiam się, że w foo = new FutureTask(), co się dzieje, gdy ktoś czekał na starym foo (dla „jakiegoś powodu”, ustaw nie został powołany, na przykład błąd w obsłudze wyjątków)?

b) Czy mogę użyć semafora:

class Event { 
    Semaphore sem; 
    Event() { sem = new Semaphore(1); sem . } 
    void signal() { sem.release(); } 
    void reset() { sem.acquire(1); } 
    void wait() { if (sem.tryAcquire(1)) { sem.release(); } } 
} 

Ale obawiam się, że jest jakaś sytuacja wyścigu, jeśli wiele wątków są wait() ing dla niego natomiast drugi sygnał() s i reset () s.

Pytanie:

Czy nic w API Java, który przypomina zachowanie zdarzeń systemu Windows? Lub, jeśli gardzisz Windows, coś w stylu golang's WaitGroup (to znaczy CountDownLatch, który zezwala na countUp())? Byle co?

Jak to zrobić ręcznie:

Temat 2 nie może po prostu poczekać z powodu fałszywej wznawianiu i Java nie ma sposobu, aby wiedzieć, dlaczego Object.wait() zwracane. Potrzebuję zmiennej warunkowej, która zapisuje, czy zdarzenie jest sygnalizowane, czy nie. Wątek 2:

synchronized(foo) { 
    while(!condition) { 
     foo.wait(); 
    } 
} 

I wątek 1 oczywiście ustawia warunek na true w zsynchronizowanym bloku. Dzięki tygodniom na podpowiedź!

Czy istnieje klasa, która owija to zachowanie?

Czy muszę skopiować i wkleić cały kod?

+0

Jaki problem rzeczywiście stara się rozwiązać? Chociaż jestem pewien, że ten problem można rozwiązać, nadal chciałbym zapytać, więc wiemy, że jest to właściwe rozwiązanie Twojego problemu. Ten rodzaj bałaganu z wątkami i semaforami, mimo że jest dobrze zrozumiany, jest często podatny na błędy z powodu drobnych błędów, więc może istnieć bardziej czytelny, łatwy do zrozumienia sposób robienia tego, co chcesz osiągnąć. W szczególności brzmi to jak zwykły wzorzec głośnika, w którym słuchacze rejestrują się ze źródłem zdarzeń, byłoby bardziej odpowiednie. – Thor84no

+0

Tak, detektor zdarzeń rozwiąże problem, umożliwiając aktualizację wątku 1 "push". Jest to zwykle preferowana metoda, np. w środowisku serwera. Jednak od czasu do czasu natknąłem się na problem "ciągnięcia" za aktualizację wątku i do tej pory nie miałem czystego rozwiązania. – Kosta

Odpowiedz

18

Standardową praktyką jest zmiana stanu podczas wykonywania funkcji notifyAll i sprawdzenie stanu podczas wykonywania wait().

np.

boolean ready = false; 

// thread 1 
synchronized(lock) { 
    ready = true; 
    lock.notifyAll(); 
} 


// thread 2 
synchronized(lock) { 
    while(!ready) 
     lock.wait(); 
} 

Przy takim podejściu nie ma znaczenia, czy wątek 1, czy wątek 2 uzyskają blokadę jako pierwszą.

Niektóre narzędzia do analizy kodu dostarczą ostrzeżenia, jeśli używasz powiadamiania lub czekania bez ustawiania wartości lub sprawdzania wartości.

+0

Tak, to jest to. Dzięki! – Kosta

+2

Czy to nie prowadzi do sytuacji zakleszczenia, w której wątek 2 chwyta 'blokadę' i nie zostanie zwolniony, dopóki nie opuści bloku (ale nie odejdzie, ponieważ oczekuje na prawdę' gotowe', co nigdy się nie wydarzy podczas gdy wątek 2 zawiera "blokadę")? – Thor84no

+3

'lock.wait()' zwalnia 'lock' i odzyskuje je przed powrotem. –

2

Najprostszym sposobem jest po prostu powiedzieć

firstThread.join();

To będzie blokowanie aż pierwszy wątek jest zakończony.

Ale można zaimplementować to samo za pomocą wait/notify. Niestety nie opublikowałeś prawdziwych fragmentów kodu, ale domyślam się, że jeśli oczekiwanie nie zakończy się po wywołaniu powiadomienia, dzieje się tak, ponieważ nie wstawiłeś obu do bloku synchronized. Zwróć uwagę, że "argument" bloku synchronized musi być taki sam dla pary wait/notify.

+0

Przepraszamy, ale w tym przypadku pierwszy wątek działa w pętli while w nieskończoność, więc firstThread.join() po prostu działa wiecznie. wait() nie kończy działania, ponieważ jest wywoływana po funkcji notifyAll(). Zsynchronizowane bloki i wait()/notifyAll() mają te same argumenty. – Kosta

3

Możesz użyć funkcji wait() z limitem czasu, w którym to przypadku nie ryzykujesz czekania na zawsze. Zwróć też uwagę, że funkcja wait() może powrócić, nawet jeśli w ogóle nie było powiadomienia(), więc musisz zawinąć czekanie w pętli warunkowej. To standardowy sposób oczekiwania w Javie.

synchronized(syncObject) { 
    while(condition.isTrue()) { 
     syncObject.wait(WAIT_TIMEOUT); 
    } 
} 

(w wątku 2)

Edit: Przeniesiony zsynchronizowane poza pętlą.

+0

Problem polega na tym, że wątek 2 pomija pierwszy alertAll(), limit czasu nie pomaga przeciwko temu. Ale: brakowało mi fałszywego przebudzenia w moim pytaniu, więc muszę to wyjaśnić. Zmienna warunkowa jest w rzeczywistości tym, czego potrzebuję - ale ja wolę, gdy (condition.isFalse()) – Kosta

+0

Tak, oczywiście możesz użyć isFalse(). Również pętla while może znajdować się w sekcji zsynchronizowanej. Ten fragment kodu służy jedynie do przedstawienia ogólnego pomysłu. – weekens

+0

Problem polega na tym, że warunek może stać się prawdziwy po sprawdzeniu, ale przed uzyskaniem blokady. Oznacza to, że kończysz oczekiwanie() bez powodu. –

2

Użyłbym BlockingQueue między dwoma wątkami. Korzystanie wait i notify jest tak 5 minut temu;)

enum Event { 
    Event, 
    Stop; 
} 

BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>(); 

// Thread 1 
try { 
    while(true) { 
    ...do something... 
    queue.put(Event.Event); 
    ...wait for some condition that might never happen... 
    ... 
    } 
} finally { 
    // Tell other thread we've finished. 
    queue.put(Event.Stop}; 
} 

// Thread 2 
... 
switch (queue.take()) { 
    case Event: 
     ... 
     break; 

    default: 
     ... 
     break; 
} 
+0

To działa, ale nie podoba mi się to, że wątek 1 musi być świadomy, ile wątków na niego czeka: Jeśli masz n wątków, które są podejmowane(), wątek 1 musi umieścić zdarzenia n – Kosta

+0

@Kosta - czy mógłbyś rozszerzyć ten wymóg w swoim pytaniu? Jestem pewien, że możemy również zapewnić tę funkcjonalność. – OldCurmudgeon

+0

Myślę, że możesz również sprawdzić CyclicBarrier - zajmie się wszystkimi zadaniami, które wymieniłeś i nie wymaga też zbyt niskiego poziomu kodu :) - http://docs.oracle.com/javase/6/docs/api/ java/util/concurrent/CyclicBarrier.html –