Como usar esperar e notificar em Java sem IllegalMonitorStateException?

129

Eu tenho 2 matrizes e preciso multiplicá-las e depois imprimir os resultados de cada célula. Assim que uma célula estiver pronta, preciso imprimi-la, mas, por exemplo, preciso imprimir a célula [0] [0] antes da célula [2] [0], mesmo que o resultado de [2] [0] esteja pronto primeiro . Então, eu preciso imprimi-lo por encomenda. Então, minha idéia é fazer com que o encadeamento da impressora aguarde até multiplyThreadque ele notifique que a célula correta está pronta para ser impressa e, em seguida printerThread, imprima a célula e volte a aguardar e assim por diante.

Então, eu tenho esse segmento que faz a multiplicação:

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

Segmento que imprime o resultado de cada célula:

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

Agora isso me lança essas exceções:

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)

a linha 49 multiplyThreadé o "notify ()". Acho que preciso usar o sincronizado de maneira diferente, mas não sei como.

Se alguém puder ajudar esse código a funcionar, eu realmente aprecio isso.

Radiodef
fonte

Respostas:

215

Para poder chamar notify (), você precisa sincronizar no mesmo objeto.

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

/* different thread / object */
synchronized (someObject) {
    someObject.notify();
}
Bombe
fonte
29
A while(!JobCompleted);opção é geralmente uma má idéia porque ele amarra o seu CPU em 100% a verificação da mesma variável constante (ver aqui )
Matt Lyons
5
while(!JobCompleted) Thread.sleep(5); não tem esse problema #
BeniBela 25/12
15
Ainda tem o problema de ser algo completamente diferente. A pesquisa (verificação repetida se alguma condição é atendida, ou seja, o que você está fazendo) geralmente é menos preferida do que ser notificada se a condição for alterada (ou seja, o que eu descrevi na resposta).
amigos estão dizendo
3
@huseyintugrulbuyukisik você pode chamar waitsempre que o encadeamento atual tiver um bloqueio no objeto no qual waité chamado. Se você usa um synchronizedbloco ou um método sincronizado, é com você.
Bombe
1
@BeniBela Mas pode-se esperar que seja mais lento, com certeza (em relação à pergunta original de Huseyin).
Thomas
64

Ao usar os métodos waite notifyou notifyAllem Java, as seguintes coisas devem ser lembradas:

  1. Use em notifyAllvez de notifyse você espera que mais de um encadeamento esteja esperando por um bloqueio.
  2. Os métodos waite notifydevem ser chamados em um contexto sincronizado . Veja o link para uma explicação mais detalhada.
  3. Sempre chame o wait()método em um loop, porque se vários threads estiverem aguardando um bloqueio e um deles tiver o bloqueio e redefinir a condição, os outros threads precisarão verificar a condição depois de acordarem para ver se precisam esperar novamente ou pode iniciar o processamento.
  4. Use o mesmo objeto para chamada wait()e notify()método; todo objeto tem seu próprio bloqueio, portanto, chamar o wait()objeto A e o notify()objeto B não fará nenhum sentido.
Jackob
fonte
21

Você precisa enfiar isso? Eu estou querendo saber o tamanho das suas matrizes e se há algum benefício em imprimir um thread enquanto o outro faz a multiplicação.

Talvez valesse a pena medir esse tempo antes de executar o trabalho de rosqueamento relativamente complexo?

Se você precisar encadear, eu criaria 'n' threads para executar a multiplicação das células (talvez 'n' seja o número de núcleos disponíveis para você) e, em seguida, use o mecanismo ExecutorService and Future para despachar várias multiplicações simultaneamente .

Dessa forma, você pode otimizar o trabalho com base no número de núcleos e usar as ferramentas de encadeamento Java de nível superior (o que deve facilitar a vida). Escreva os resultados novamente em uma matriz de recebimento e, em seguida, simplesmente imprima isso quando todas as suas tarefas futuras forem concluídas.

Brian Agnew
fonte
1
+1 @Greg Acho que você deve dar uma olhada no pacote java.util.concurrent, como apontado por Brian.
ATorras 20/05/09
1
+1 e também verificar este livro que também vai te ensinar a maneira correta de espera uso () e notificar () jcip.net
Chii
14

Digamos que você tenha o aplicativo 'black box' com alguma classe nomeada BlackBoxClassque possui método doSomething();.

Além disso, você tem um observador ou ouvinte chamado onResponse(String resp)que será chamado BlackBoxClassapós um tempo desconhecido.

O fluxo é simples:

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

Digamos que não sabemos o que está acontecendo BlackBoxClasse quando devemos obter resposta, mas você não deseja continuar seu código até receber resposta ou, em outras palavras, receber uma onResponseligação. Aqui entra 'Synchronize helper':

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

Agora podemos implementar o que queremos:

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

}
Maxim Shoustin
fonte
7

Você só pode ligar para notificar sobre objetos onde possui o monitor deles. Então você precisa de algo como

synchronized(threadObject)
{
   threadObject.notify();
}
PaulJWilliams
fonte
6

notify() precisa ser sincronizado também

takete.dk
fonte
3

Vou mostrar um exemplo simples da maneira correta de usar waite notifyem Java. Então, eu vou criar duas classes denominadas ThreadA e ThreadB . ThreadA chamará 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);
        }
    }
} 

e para a classe 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
            }
        }
    }
BERGUIGA Mohamed Amine
fonte
3

Uso simples se você quiser Como executar threads alternativamente: -

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

resposta :-

T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
T1:A
T2:B
Opster Elasticsearch Pro-Vijay
fonte
Como isso funciona quando eu tenho quatro operações para executar de maneira síncrona?
saksham Agarwal
2

podemos ligar para notificar para retomar a execução de objetos em espera como

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

retome isso chamando notificar em outro objeto da mesma classe

public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}
Greesh Kumar
fonte
0

Para esse problema específico, por que não armazenar seus vários resultados em variáveis ​​e, quando o último de seu thread for processado, você poderá imprimir no formato que desejar. Isso é especialmente útil se você estiver usando seu histórico de trabalho em outros projetos.

kbluue
fonte
0

Parece uma situação para o padrão produtor-consumidor. Se você estiver usando o java 5 ou superior, considere o uso da fila de bloqueio (java.util.concurrent.BlockingQueue) e deixe o trabalho de coordenação do encadeamento na implementação subjacente da estrutura / API. Veja o exemplo do java 5: http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html ou java 7 (mesmo exemplo): http: // docs. oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html

user3044236
fonte
0

Você protegeu adequadamente seu bloco de código ao chamar o wait()método usando synchronized(this).

Mas você não tomou a mesma precaução ao chamar o notify()método sem usar o bloco protegido: synchronized(this)ousynchronized(someObject)

Se você consultar a página de documentação do Oracle no objeto classe, que contém wait(), notify(), notifyAll()métodos, você pode ver abaixo precaução em todos esses três métodos

Esse método deve ser chamado apenas por um encadeamento que é o proprietário do monitor desse objeto

Muitas coisas foram alteradas nos últimos 7 anos e vamos procurar outras alternativas para as synchronizedperguntas abaixo do SE:

Por que usar um ReentrantLock se é possível usar sincronizado (isso)?

Sincronização vs Bloqueio

Evitar sincronizado (isso) em Java?

Ravindra babu
fonte