IllegalMonitorStateException w wywołaniu wait ()


162

W moim programie używam wielowątkowości w Javie. Uruchomiłem wątek pomyślnie, ale kiedy używam Thread.wait(), rzuca java.lang.IllegalMonitorStateException. Jak mogę sprawić, by wątek czekał, aż zostanie powiadomiony?


2
Thread.wait () nie istnieje, to może być this.wait ()
Premraj

Odpowiedzi:


175

Aby pracować, musisz być w synchronizedbloku Object.wait().

Polecam również przyjrzenie się pakietom współbieżności zamiast pakietów wątków starej szkoły. Są bezpieczniejsze i łatwiejsze w obsłudze .

Miłego kodowania.

EDYTOWAĆ

Założyłem, że Object.wait()jako wyjątek miałeś na myśli to, co dzieje się, gdy próbujesz uzyskać dostęp bez blokowania obiektów.


1
dobry chwyt. Zakładałem, że miał na myśli Object.wait () i zadzwoniłem z wątku
reccles

2
Zsynchronizowany blok na obiekcie, na który czekasz. Chcesz zmienić tę odpowiedź, aby była bardziej zrozumiała? Dzięki.
Gray

55

waitjest zdefiniowany w programie Object, a nie w nim Thread. Monitor włączony Threadjest trochę nieprzewidywalny.

Chociaż wszystkie obiekty Java mają monitory, generalnie lepiej jest mieć dedykowaną blokadę:

private final Object lock = new Object();

Możesz uzyskać nieco łatwiejszą do odczytania diagnostykę przy niewielkim koszcie pamięci (około 2 KB na proces), używając nazwanej klasy:

private static final class Lock { }
private final Object lock = new Lock();

Aby obiekt waitlub notify/ notifyAllobiekt, musisz trzymać blokadę z synchronizedinstrukcją. Będziesz także potrzebował whilepętli, aby sprawdzić stan wybudzania (znajdź dobry tekst na temat wątków, aby wyjaśnić, dlaczego).

synchronized (lock) {
    while (!isWakeupNeeded()) {
        lock.wait();
    }
}

Powiadamiać:

synchronized (lock) {
    makeWakeupNeeded();
    lock.notifyAll();
}

Podczas wchodzenia w wielowątkowość warto poznać zarówno język Java, jak i java.util.concurrent.locksblokady (i java.util.concurrent.atomic). Ale używaj java.util.concurrentstruktur danych, kiedy tylko możesz.


5
Nigdy nie rozumiałem, jak to działa, biorąc pod uwagę, że zarówno oczekiwanie, jak i powiadomienie znajdują się w zsynchronizowanych blokach tego samego obiektu (blokada). Ponieważ wątek oczekiwania znajduje się w bloku, czy nie powinno to powodować bloku powiadamiającego wątku w wierszu „zsynchronizowany (zablokowany)”?
Brent212

6
@ Brent212 W przypadku jakiejkolwiek metody innej niż wait, tak, nigdy byś tego nie zrobił notify. Jednak w dokumentacji API dla Object.wait„Wątek zwalnia własność tego monitora”. Więc gdy jest w środku wait, jest tak, jakby był poza otaczającymi synchronizedblokami (dla tego samego obiektu może być wiele synchronizedbloków na tym samym obiekcie).
Tom Hawtin - tackline

24

Wiem, że ten wątek ma prawie 2 lata, ale nadal muszę go zamknąć, ponieważ przyszedłem również do tej sesji Q / A z tym samym problemem ...

Przeczytaj tę definicję nielegalnego wyjątku MonitorException raz po raz ...

IllegalMonitorException jest generowany w celu wskazania, że ​​wątek próbował czekać na monitorze obiektu lub aby powiadomić inne wątki oczekujące na monitorze obiektu bez posiadania określonego monitora.

Ta linia wielokrotnie mówi, że IllegalMonitorException pojawia się, gdy wystąpi jedna z 2 sytuacji ...

1> czekaj na monitorze obiektu bez posiadania określonego monitora.

2> powiadomić inne wątki oczekujące na monitorze obiektu bez posiadania określonego monitora.

Niektórzy mogliby mieć odpowiedzi ... a kto nie, sprawdź 2 stwierdzenia ...

zsynchronizowany (obiekt)

object.wait ()

Jeśli oba obiekty są takie same ... wtedy nie może nadejść żaden nielegalny wyjątek MonitorException.

Teraz ponownie przeczytaj definicję IllegalMonitorException i nie zapomnisz o tym ponownie ...


Właściwie to nie działa. Próbowałem tego. Tworzę Runnable, blokuję go (używając zsynchronizowanego bloku) i wewnątrz tego bloku uruchamiam Runnable w wątku interfejsu użytkownika (Android), a następnie robię myRunnable.wait () i nadal otrzymuję wyjątek.
Ted

Doskonałe wyjaśnienie !! Robiłem wait () bez określenia obiektu, więc zajęło to wystąpienie i synchronizację na innym obiekcie. Teraz używam otherObject.wait () i to działa!
Fersca

6

Na podstawie twoich komentarzy wygląda na to, że robisz coś takiego:

Thread thread = new Thread(new Runnable(){
    public void run() { // do stuff }});

thread.start();
...
thread.wait();

Są trzy problemy.

  1. Jak powiedzieli inni, obj.wait()można go wywołać tylko wtedy, gdy bieżący wątek przechowuje pierwotną blokadę / mutex dla obj. Jeśli bieżący wątek nie utrzymuje blokady, pojawi się wyjątek, który widzisz.

  2. thread.wait()Połączenie nie robi tego, co wydaje się być spodziewałem się zrobić. W szczególności thread.wait() nie powoduje, że nominowany wątek czeka. Raczej powoduje, że bieżący wątek czeka, aż inne wywołania wątku thread.notify()lub thread.notifyAll().

    W rzeczywistości nie ma bezpiecznego sposobu na zmuszenie Threadinstancji do wstrzymania, jeśli nie chce. (Najbliżej tego, co ma Java, jest Thread.suspend()metoda przestarzała , ale ta metoda jest z natury niebezpieczna, jak wyjaśniono w Javadoc.)

    Jeśli chcesz, aby nowo rozpoczęty Threadpauzował, najlepszym sposobem na to jest utworzenie CountdownLatchinstancji i wywołanie wątku await()na zatrzasku, aby wstrzymało się. Główny wątek wywołałby wówczas countDown()zatrzask, aby umożliwić kontynuację wstrzymanego wątku.

  3. Prostopadłe do poprzednich punktów, użycie Threadobiektu jako blokady / muteksu może powodować problemy. Na przykład javadoc dla Thread::joinmówi:

    Ta implementacja używa pętli this.waitwywołań uwarunkowanych this.isAlive. Gdy wątek się kończy, this.notifyAllwywoływana jest metoda. Zaleca się, aby nie korzystać z aplikacji wait, notifylub notifyAllna Threadinstancjach.


2

Ponieważ nie opublikowałeś kodu, pracujemy po omacku. Jakie są szczegóły wyjątku?

Czy wywołujesz Thread.wait () z poziomu wątku, czy poza nim?

Pytam o to, ponieważ zgodnie z javadoc dla IllegalMonitorStateException jest to:

Zgłaszane, aby wskazać, że wątek próbował czekać na monitorze obiektu lub powiadamiać inne wątki oczekujące na monitorze obiektu bez posiadania określonego monitora.

Aby wyjaśnić tę odpowiedź, to wywołanie oczekiwania na wątek zgłasza również wyjątek IllegalMonitorStateException, mimo że jest wywoływany z zsynchronizowanego bloku:


     private static final class Lock { }
     private final Object lock = new Lock();

    @Test
    public void testRun() {
        ThreadWorker worker = new ThreadWorker();
        System.out.println ("Starting worker");
        worker.start();
        System.out.println ("Worker started - telling it to wait");
        try {
            synchronized (lock) {
                worker.wait();
            }
        } catch (InterruptedException e1) {
            String msg = "InterruptedException: [" + e1.getLocalizedMessage() + "]";
            System.out.println (msg);
            e1.printStackTrace();
            System.out.flush();
        }
        System.out.println ("Worker done waiting, we're now waiting for it by joining");
        try {
            worker.join();
        } catch (InterruptedException ex) { }

    }

@CPerkins: Myślę, że mylisz wątek wykonawczy z obiektem, który jest celem wait().
Robert Munteanu

@Robert - Być może tak, ale nie sądzę. Jeśli uruchomisz instancję Thread, a następnie poprosisz ją o czekanie, otrzymasz wyjątek IllegalMonitorStateException, który właśnie próbowałem opisać.
CPerkins

Mówisz o worker.wait()linii? Wtedy powinieneś synchronizować się z pracownikiem, a nie z zamkiem.
Robert Munteanu

1

Aby poradzić sobie z wyjątkiem IllegalMonitorStateException, należy sprawdzić, czy wszystkie wywołania metod wait, notification i notifyAll mają miejsce tylko wtedy, gdy wątek wywołujący jest właścicielem odpowiedniego monitora . Najprostszym rozwiązaniem jest umieszczenie tych wywołań w zsynchronizowanych blokach. Obiekt synchronizacji, który ma zostać wywołany w zsynchronizowanej instrukcji, to ten, którego monitor należy pobrać.

Oto prosty przykład zrozumienia pojęcia monitora

public class SimpleMonitorState {

    public static void main(String args[]) throws InterruptedException {

        SimpleMonitorState t = new SimpleMonitorState();
        SimpleRunnable m = new SimpleRunnable(t);
        Thread t1 = new Thread(m);
        t1.start();
        t.call();

    }

    public void call() throws InterruptedException {
        synchronized (this) {
            wait();
            System.out.println("Single by Threads ");
        }
    }

}

class SimpleRunnable implements Runnable {

    SimpleMonitorState t;

    SimpleRunnable(SimpleMonitorState t) {
        this.t = t;
    }

    @Override
    public void run() {

        try {
            // Sleep
            Thread.sleep(10000);
            synchronized (this.t) {
                this.t.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

0

Wywołanie Thread.wait () ma sens w kodzie, który synchronizuje się z obiektem Thread.class. Nie sądzę, że to miałeś na myśli.
Ty pytasz

Jak mogę sprawić, by wątek czekał, aż zostanie powiadomiony?

Możesz sprawić, by czekał tylko bieżący wątek. Każdy inny wątek może zostać delikatnie poproszony o czekanie, jeśli się zgodzi.
Jeśli chcesz poczekać na jakiś warunek, potrzebujesz obiektu blokującego - obiekt Thread.class to bardzo zły wybór - jest to singleton AFAIK, więc synchronizacja na nim (poza metodami statycznymi Thread) jest niebezpieczna.
Szczegóły dotyczące synchronizacji i oczekiwania zostały już wyjaśnione przez Toma Hawtina. java.lang.IllegalMonitorStateExceptionoznacza, że ​​próbujesz czekać na obiekt, z którym nie jesteś zsynchronizowany - jest to nielegalne.


0

Nie jestem pewien, czy to pomoże komuś innemu, czy nie, ale to była kluczowa część rozwiązania mojego problemu w odpowiedzi użytkownika „Tom Hawtin - tacklin” powyżej:

synchronized (lock) {
    makeWakeupNeeded();
    lock.notifyAll();
}

Po prostu fakt, że "lock" jest przekazywany jako argument w synchronized () i jest również używany w "lock" .notifyAll ();

Kiedy już zrobiłem to w tych 2 miejscach, to działało


0

Przez IllegalMonitorStateExceptionchwilę próbowałem obudzić wątek w / z innego class/ wątku. W java 8można wykorzystać lockmożliwości nowego Współbieżnym API zamiast z synchronizedfunkcji.

Przechowywałem już obiekty dla asynchronoustransakcji WebSocket w pliku WeakHashMap. Rozwiązaniem w moim przypadku było również przechowywanie lockobiektu w aConcurrentHashMap dla synchronousodpowiedzi. Zwróć uwagę na condition.await(nie .wait).

Aby obsłużyć wielowątkowość, użyłem a Executors.newCachedThreadPool()do utworzenia puli wątków .


0

Ci, którzy używają Javy 7.0 lub niższej, mogą odwołać się do kodu, którego tutaj użyłem i działa.

public class WaitTest {

    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void waitHere(long waitTime) {
        System.out.println("wait started...");
        lock.lock();
        try {
            condition.await(waitTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        lock.unlock();
        System.out.println("wait ends here...");
    }

    public static void main(String[] args) {
        //Your Code
        new WaitTest().waitHere(10);
        //Your Code
    }

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.