Как использовать ожидание и уведомление в Java без исключения IllegalMonitorStateException?


129

У меня есть 2 матрицы, и мне нужно их умножить, а затем распечатать результаты каждой ячейки. Как только одна ячейка будет готова, мне нужно ее распечатать, но, например, мне нужно распечатать ячейку [0] [0] перед ячейкой [2] [0], даже если результат [2] [0] готов первым , Поэтому мне нужно распечатать его по заказу. Итак, моя идея состоит в том, чтобы заставить поток принтера ждать, пока multiplyThreadон не уведомит его о том, что правильная ячейка готова к печати, а затем printerThreadраспечатает ячейку и вернется в режим ожидания и так далее.

Итак, у меня есть этот поток, который выполняет умножение:

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();
}

Поток, выводящий результат каждой ячейки:

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] + " ");
            }
        }
    }

Теперь это выдает мне следующие исключения:

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)

строка 49 multiplyThread- это «notify ()» .. Я думаю, мне нужно использовать синхронизированный код по-другому, но я не уверен, как это сделать.

Если кто-то может помочь этому коду работать, я буду очень признателен.

Ответы:


215

Чтобы иметь возможность вызывать notify (), вам необходимо выполнить синхронизацию с тем же объектом.

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

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

29
Этот while(!JobCompleted);вариант, как правило, плохая идея, потому что он заставляет ваш процессор на 100% постоянно проверять одну и ту же переменную (см. Здесь )
Мэтт Лайонс,

5
while(!JobCompleted) Thread.sleep(5); нет такой проблемы
BeniBela

15
У него все еще есть проблема быть чем-то совершенно другим. Опрос (многократная проверка того, выполняется ли какое-либо условие, например, что вы делаете), как правило, менее предпочтителен, чем получение уведомлений об изменении указанного условия (т.е. о том, что я указал в ответе).
Bombe

3
@huseyintugrulbuyukisik можно вызывать waitвсякий раз, когда текущий поток блокирует объект, который waitвызывается. Независимо от того, используете ли вы synchronizedблочный или синхронизированный метод, все зависит от вас.
Bombe

1
@BeniBela Но наверняка можно ожидать, что он будет медленнее (относительно исходного вопроса Хусейна).
Thomas

64

При использовании методов waitи notifyили notifyAllв Java необходимо помнить следующее:

  1. Используйте notifyAllвместо, notifyесли вы ожидаете, что более одного потока будут ожидать блокировки.
  2. Эти waitи notifyметоды должны быть вызваны в синхронном контексте . См. Ссылку для более подробного объяснения.
  3. Всегда вызывайте wait()метод в цикле, потому что, если несколько потоков ожидают блокировки, и один из них получил блокировку и сбросил условие, тогда другие потоки должны проверить условие после того, как они проснутся, чтобы узнать, нужно ли им снова ждать или можно начать обработку.
  4. Используйте один и тот же объект для вызова wait()и notify()метода; у каждого объекта есть собственная блокировка, поэтому вызов wait()объекта A и notify()объекта B не имеет смысла.

21

Вам вообще нужно это нить? Мне интересно, насколько велики ваши матрицы, и есть ли какая-то польза от печати одним потоком, в то время как другой выполняет умножение.

Возможно, стоит измерить это время, прежде чем выполнять относительно сложную работу с потоками?

Если вам действительно нужно его распределять, я бы создал n потоков для выполнения умножения ячеек (возможно, n - это количество доступных вам ядер), а затем использовал бы ExecutorService и механизм Future для одновременной отправки нескольких умножений ,

Таким образом, вы можете оптимизировать работу в зависимости от количества ядер, и вы используете инструменты потоковой передачи Java более высокого уровня (которые должны облегчить жизнь). Запишите результаты обратно в приемную матрицу, а затем просто распечатайте ее, когда все ваши будущие задачи будут выполнены.


1
+1 @Greg Я думаю, вам стоит взглянуть на пакет java.util.concurrent, как указал Брайан.
ATorras

1
+1, а также ознакомьтесь с этой книгой, которая также научит вас, как правильно использовать wait () и notify
Chii

14

Допустим, у вас есть приложение «черный ящик» с некоторым классом BlackBoxClass, имеющим метод doSomething();.

Кроме того, у вас есть названный наблюдатель или слушатель, onResponse(String resp)который будет вызван BlackBoxClassчерез неизвестное время.

Схема проста:

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

Допустим, мы не знаем, что происходит BlackBoxClassи когда мы должны получить ответ, но вы не хотите продолжать свой код, пока не получите ответ или, другими словами, не onResponseпозвоните. Здесь вводится «Помощник по синхронизации»:

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) {
        }
    }
}
}

Теперь мы можем реализовать то, что хотим:

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

Вы можете вызвать уведомление только для объектов, монитор которых у вас есть. Итак, вам нужно что-то вроде

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


3

Я прямо сейчас на простом примере покажу, как правильно использовать waitи notifyв Java. Итак, я создам два класса с именами ThreadA и ThreadB . ThreadA вызовет 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);
        }
    }
} 

и для класса 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

Простое использование, если вы хотите Как альтернативно выполнять потоки: -

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();
    }
}

ответ :-

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

Как это работает, когда мне нужно выполнить 4 операции синхронно?
saksham agarwal

2

мы можем вызвать notify, чтобы возобновить выполнение ожидающих объектов, как

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!");
}

возобновить это, вызвав уведомление для другого объекта того же класса

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

0

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


0

Это похоже на ситуацию для модели производитель-потребитель. Если вы используете java 5 или более позднюю версию, вы можете рассмотреть возможность использования очереди блокировки (java.util.concurrent.BlockingQueue) и оставить работу по координации потоков для реализации базового фреймворка / api. См. Пример из java 5: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html или java 7 (тот же пример): http: // docs. oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html


0

Вы правильно защитили свой блок кода при вызове wait()метода с помощьюsynchronized(this) .

Но вы не приняли такой же меры предосторожности при вызове notify()метода без использования защищенного блока: synchronized(this)илиsynchronized(someObject)

Если вы обратитесь к оракул странице документации на Объект класса, который содержит wait(), notify(), notifyAll()методы, вы можете увидеть ниже меры предосторожности во всех этих трех методов

Этот метод должен вызываться только потоком, который является владельцем монитора этого объекта.

За последние 7 лет многое изменилось, и давайте рассмотрим другие альтернативы synchronizedв следующих вопросах SE:

Зачем использовать ReentrantLock, если можно использовать synchronized (this)?

Синхронизация против блокировки

Избегайте синхронизации (этого) в Java?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.