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

У меня есть 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()".. Я думаю, что мне нужно использовать синхронизированный по-другому, но я не уверен, как.

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

12 ответов

Решение

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

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

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

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

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

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

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

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

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

Допустим, у вас есть приложение "черный ящик" с некоторым классом с именем 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();       
   }

}

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

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

notify() также необходимо синхронизировать

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

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

Я приведу простой пример, который покажет вам, как правильно пользоваться wait а также notify на Яве. Поэтому я создам два класса с именем 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
            }
        }
    }

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

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

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

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

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

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

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

Зачем использовать ReentrantLock, если можно использовать синхронизированный (это)?

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

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

Это похоже на ситуацию для модели производитель-потребитель. Если вы используете Java 5 или более позднюю версию, вы можете рассмотреть возможность использования очереди блокировки (java.util.concurrent.BlockingQueue) и оставить работу по координации потоков для базовой реализации framework/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

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

Другие вопросы по тегам