Як використовувати очікування та сповіщення на 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

Щоб мати можливість викликати сповіщення (), вам потрібно синхронізувати один і той же об’єкт.

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

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

29
Цей while(!JobCompleted);варіант, як правило, погана ідея, оскільки він пов’язує ваш процесор на 100%, постійно перевіряючи ту саму змінну (див. Тут )
Метт Ліонс

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

15
У ньому ще є проблема бути чимось зовсім іншим. Опитування (неодноразово перевіряючи, чи виконується якась умова, тобто те, що ви робите), як правило, менш бажане, ніж повідомлення, якщо змінено цю умову (тобто те, що я окреслив у відповіді).
Бомбе

3
@huseyintugrulbuyukisik ви можете дзвонити, waitколи поточна нитка має блокування на об'єкті, на який waitвикликається. Чи будете ви використовувати synchronizedблок або синхронізований метод, повністю залежить від вас.
Бомбе

1
@BeniBela Але, напевно, можна сподіватися, що це буде повільніше (щодо оригінального запитання Хусейна).
Томас

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, як зазначив Брайан.
Аторрас

1
+1, а також ознайомтеся з цією книгою, яка також навчить вас правильному способу користування wait () та сповістити () jcip.net
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

ми можемо зателефонувати сповістити про відновлення виконання очікуваних об'єктів як

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) і залишити роботу по координації потоків під базовою реалізацією frame / 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, якщо можна використовувати синхронізовану (це)?

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

Уникайте синхронізації (цього) на Java?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.