Jak używać czekania i powiadamiania w Javie bez wyjątku IllegalMonitorStateException?


129

Mam 2 macierze i muszę je pomnożyć, a następnie wydrukować wyniki z każdej komórki. Jak tylko jedna komórka jest gotowa, muszę ją wydrukować, ale na przykład muszę wydrukować komórkę [0] [0] przed komórką [2] [0], nawet jeśli wynik [2] [0] jest najpierw gotowy . Więc muszę to wydrukować na zamówienie. Więc moim pomysłem jest, aby wątek drukarki czekał, aż multiplyThreadpowiadomi go, że właściwa komórka jest gotowa do wydrukowania, a następnie printerThreadwydrukuje komórkę i wróci do oczekiwania i tak dalej.

Więc mam ten wątek, który wykonuje mnożenie:

public void run() 
{
    int countNumOfActions = 0; // How many multiplications have we done
    int maxActions = randomize(); // Maximum number of actions allowed

    for (int i = 0; i < size; i++)
    {       
        result[rowNum][colNum] = result[rowNum][colNum] + row[i] * col[i];
        countNumOfActions++;
        // Reached the number of allowed actions
        if (countNumOfActions >= maxActions)
        {
            countNumOfActions = 0;
            maxActions = randomize();
            yield();
        }   
    }
    isFinished[rowNum][colNum] = true;
    notify();
}

Wątek, który drukuje wynik każdej komórki:

public void run()
{
    int j = 0; // Columns counter
    int i = 0; // Rows counter
    System.out.println("The result matrix of the multiplication is:");

    while (i < creator.getmThreads().length)
    {
        synchronized (this)
        {
            try 
            {
                this.wait();
            } 
            catch (InterruptedException e1) 
            {
            }
        }
        if (creator.getmThreads()[i][j].getIsFinished()[i][j] == true)
        {
            if (j < creator.getmThreads()[i].length)
            {
                System.out.print(creator.getResult()[i][j] + " ");
                j++;
            }
            else
            {
                System.out.println();
                j = 0;
                i++;
                System.out.print(creator.getResult()[i][j] + " ");
            }
        }
    }

Teraz rzuca mi te wyjątki:

Exception in thread "Thread-9" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-6" Exception in thread "Thread-4" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-5" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-8" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-7" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-11" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-10" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)
Exception in thread "Thread-12" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at multiplyThread.run(multiplyThread.java:49)

wiersz 49 w multiplyThreadto „notify ()”. Myślę, że muszę inaczej używać synchronizacji, ale nie jestem pewien, jak to zrobić.

Jeśli ktoś może pomóc temu kodowi działać, to naprawdę to docenię.

Odpowiedzi:


216

Aby móc wywoływać powiadomienie () , musisz zsynchronizować ten sam obiekt.

synchronized (someObject) {
    someObject.wait();
}

/* different thread / object */
synchronized (someObject) {
    someObject.notify();
}

29
Ta while(!JobCompleted);opcja jest ogólnie złym pomysłem, ponieważ wiąże procesor w 100%, sprawdzając stale tę samą zmienną (patrz tutaj )
Matt Lyons

5
while(!JobCompleted) Thread.sleep(5); nie ma tego problemu
BeniBela

15
Nadal ma problem bycia czymś zupełnie innym. Sondowanie (wielokrotne sprawdzanie, czy jakiś warunek jest spełniony, tj. Co robisz) jest generalnie mniej preferowane niż otrzymywanie powiadomień, jeśli ten warunek zostanie zmieniony (tj. To, co opisałem w odpowiedzi).
Bombe

3
@huseyintugrulbuyukisik możesz wywołać, waitgdy bieżący wątek ma blokadę na waitwywołanym obiekcie . To, czy użyjesz synchronizedbloku, czy metody zsynchronizowanej, zależy wyłącznie od Ciebie.
Bombe

1
@BeniBela Ale na pewno można się spodziewać, że będzie wolniej (w odniesieniu do pierwotnego pytania Huseyina).
Thomas

64

Korzystając z metod waiti notifylub notifyAllw Javie należy pamiętać o następujących rzeczach:

  1. Użyj notifyAllzamiast, notifyjeśli spodziewasz się, że więcej niż jeden wątek będzie czekał na blokadę.
  2. waitI notifymetody muszą być nazywane w zsynchronizowany kontekście . Zobacz łącze, aby uzyskać bardziej szczegółowe wyjaśnienie.
  3. Zawsze wywołuj wait()metodę w pętli, ponieważ jeśli wiele wątków czeka na blokadę, a jeden z nich otrzymał blokadę i zresetował warunek, pozostałe wątki muszą sprawdzić stan po przebudzeniu, aby zobaczyć, czy muszą ponownie czekać, czy można rozpocząć przetwarzanie.
  4. Użyj tego samego obiektu do wywołania wait()i notify()metody; każdy obiekt ma własną blokadę, więc wywołanie wait()obiektu A i notify()obiektu B nie będzie miało żadnego sensu.

21

Czy w ogóle musisz to wątkować? Zastanawiam się, jak duże są twoje macierze i czy jest jakaś korzyść z drukowania jednego wątku, podczas gdy drugi wykonuje mnożenie.

Może warto byłoby zmierzyć ten czas przed wykonaniem stosunkowo złożonej pracy związanej z gwintowaniem?

Jeśli potrzebujesz wątku, utworzę wątki `` n '', aby wykonać mnożenie komórek (być może `` n '' to liczba dostępnych rdzeni), a następnie użyj mechanizmu ExecutorService i Future do jednoczesnego wysłania wielu mnożeń .

W ten sposób możesz zoptymalizować pracę w oparciu o liczbę rdzeni i używasz wyższego poziomu narzędzi do obsługi wątków Java (co powinno ułatwić życie). Zapisz wyniki z powrotem do matrycy odbiorczej, a następnie po prostu wydrukuj ją po zakończeniu wszystkich zadań w przyszłości.


1
+1 @Greg Myślę, że powinieneś spojrzeć na pakiet java.util.concurrent, jak wskazał Brian.
ATorras

1
+1, a także zapoznaj się z tą książką, która nauczy Cię również prawidłowego sposobu korzystania z funkcji wait () i notify () jcip.net
Chii

14

Powiedzmy, że masz aplikację „czarną skrzynkę” z klasą o nazwie, BlackBoxClassktóra ma metodę doSomething();.

Ponadto masz nazwanego obserwatora lub słuchacza, onResponse(String resp)który zostanie wywołany BlackBoxClasspo nieznanym czasie.

Przepływ jest prosty:

private String mResponse = null; 
 ...
BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();
...
@override
public void onResponse(String resp){        
      mResponse = resp;       
}

Powiedzmy, że nie wiemy, co się dzieje BlackBoxClassi kiedy powinniśmy otrzymać odpowiedź, ale nie chcesz kontynuować kodu, dopóki nie otrzymasz odpowiedzi lub innymi słowy nie onResponsezadzwonisz. Tutaj wchodzi „Synchronizuj pomocnika”:

public class SyncronizeObj {
public void doWait(long l){
    synchronized(this){
        try {
            this.wait(l);
        } catch(InterruptedException e) {
        }
    }
}

public void doNotify() {
    synchronized(this) {
        this.notify();
    }
}

public void doWait() {
    synchronized(this){
        try {
            this.wait();
        } catch(InterruptedException e) {
        }
    }
}
}

Teraz możemy wdrożyć to, co chcemy:

public class Demo {

private String mResponse = null; 
 ...
SyncronizeObj sync = new SyncronizeObj();

public void impl(){

BlackBoxClass bbc = new BlackBoxClass();
   bbc.doSomething();

   if(mResponse == null){
      sync.doWait();
    }

/** at this momoent you sure that you got response from  BlackBoxClass because
  onResponse method released your 'wait'. In other cases if you don't want wait too      
  long (for example wait data from socket) you can use doWait(time) 
*/ 
...

}


@override
public void onResponse(String resp){        
      mResponse = resp;
      sync.doNotify();       
   }

}

7

Powiadomienia możesz wywoływać tylko w przypadku obiektów, do których posiadasz ich monitor. Więc potrzebujesz czegoś takiego

synchronized(threadObject)
{
   threadObject.notify();
}


3

Prawidłowy prosty przykład pokażę ci właściwy sposób używania waiti notifyw Javie. Więc utworzę dwie klasy o nazwach ThreadA i ThreadB . ThreadA wywoła ThreadB.

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();//<----Create Instance for seconde class
        b.start();//<--------------------Launch thread

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();//<-------------WAIT until the finish thread for class B finish
            }catch(InterruptedException e){
                e.printStackTrace();
            }

            System.out.println("Total is: " + b.total);
        }
    }
} 

a dla klasy ThreadB:

class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();//<----------------Notify the class wich wait until my    finish 
//and tell that I'm finish
            }
        }
    }

3

Proste użycie, jeśli chcesz Jak alternatywnie wykonywać wątki: -

public class MyThread {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "A");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T1").start();

        new Thread(() -> {
            try {
                synchronized (lock) {
                    for (int i = 0; i <= 5; i++) {
                        System.out.println(Thread.currentThread().getName() + ":" + "B");
                        lock.notify();
                        lock.wait();
                    }
                }
            } catch (Exception e) {}
        }, "T2").start();
    }
}

odpowiedź: -

T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B

Jak to działa, gdy mam 4 operacje do wykonania w sposób synchroniczny?
saksham agarwal

2

możemy wywołać powiadomienie, aby wznowić wykonywanie oczekujących obiektów jako

public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

wznowić to przez wywołanie powiadomienia na innym obiekcie tej samej klasy

public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

0

W przypadku tego konkretnego problemu dlaczego nie przechowywać różnych wyników w zmiennych, a następnie po przetworzeniu ostatniego wątku można drukować w dowolnym formacie. Jest to szczególnie przydatne, jeśli będziesz używać swojej historii pracy w innych projektach.


0

Wygląda to na sytuację dla wzorca producent-konsument. Jeśli korzystasz z Java 5 lub nowszej, możesz rozważyć użycie kolejki blokującej (java.util.concurrent.BlockingQueue) i pozostawić koordynację wątków do podstawowej implementacji frameworka / interfejsu API. Zobacz przykład z java 5: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html lub java 7 (ten sam przykład): http: // docs. oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html


0

Prawidłowo zabezpieczyłeś swój blok kodu podczas wywoływania wait()metody przy użyciu synchronized(this).

Ale nie podjąłeś tych samych środków ostrożności, gdy wywołujesz notify()metodę bez użycia chronionego bloku:synchronized(this) lubsynchronized(someObject)

Jeśli znajdują się na stronie dokumentacji systemu Oracle obiektu klasy, która zawiera wait(), notify(), notifyAll()metody, można zobaczyć poniżej ostrożności we wszystkich tych trzech metod

Ta metoda powinna być wywoływana tylko przez wątek będący właścicielem monitora tego obiektu

Wiele rzeczy zostało zmienionych w ciągu ostatnich 7 lat i spójrzmy na inne alternatywy dla synchronized poniższych pytań SE:

Po co używać ReentrantLock, jeśli można używać zsynchronizowanego (tego)?

Synchronizacja a blokada

Unikać synchronizacji (this) w Javie?

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.