Wait() e notify() duvida

3 respostas
LPJava

ae pessoal lendo o finalzinho do livro da kathy cap 9. fiquei em uma duvida desses metodo wait() e notify(). olha a sintaxe abaixo:

class ThreadA{
public static void main(String [] args){
	ThreadB b = new ThreadB();
	b.start();
	//sicronizo o objeto e o bloco do codigo
	synchronized(b){
		try{
			System.out.println("waiting for b complete...");
			b.wait();
		}catch(InterruptedException e){}
		System.out.println("Total " + b.total);
	}
}
}

class ThreadB extends Thread{
	int total;
	public void run(){
		synchronized(this){
			for(int i=0;i<3;i++){
				total+=i;
			}
			notify();
		}
	}
}

queri entender uma coisa… quando uso o wait() eu digo para Thread que sincronizei ei espere ai… ate vc receber uma notificação para continuar… seria isso?
entao minha thread na linha 9 para a execução da threadA e essa thread so continua quando a threadB b receber notify()? eu fiquei meio confuso na implementacao desses metodos… sao bastante legais mais… assim uma thread sempre fica esperando la? sem fazer nada ate que receba uma notificação?

e outro codigo que a kathy disse que se eu alterar a linha 36 para notify() apenas um thread será notificado mudei e nao vi efeito no resultado… :?

class Reader extends Thread{
	Calculator c;
	
	public Reader(Calculator calc){
		c = calc;
	}
	
	public void run(){
		synchronized(c){
			try{
				System.out.println("waiting for calculation..");
				c.wait();
			}catch(InterruptedException e){}
		}
		System.out.println("Total is " + c.total);
	}
	
	public static void main(String args[]){
		//objeto da thread
		Calculator calculator = new Calculator();
		
		new Reader(calculator).start();
		new Reader(calculator).start();
		new Reader(calculator).start();
		calculator.start();
	}
}
class Calculator extends Thread{
	int total;
	public void run(){
		synchronized(this){
			for(int i=0;i<100;i++){
				total+=1;
			}
			//libera toda as tres thread em espera de Readrs
			notifyAll();
		}
	}
}

maior duvida nesse finalizinho… quem puder dar esse help ai agradeço!!

3 Respostas

ViniGodoy

Oi Camilo.

Lembra da chave que comentei para você no outro post? Então, o comando notify()/notifyAll() serve para acordar threads que estão dormindo sob o controle daquela chave.

Ou seja, se você tiver a chave “chave” o comando:

synchronized (chave) { wait(); }
Será acordado por:

synchronized (chave) { notifyAll(); }
Agora, um detalhe. O Java está sujeito a spurious wakeup, ou seja, de vez enquando o wait() pode resolver acordar sem motivo algum. Você pode deixar o seu código mais claro e evitar esse problema associando uma condição ao wait(), através de um while:

synchronized (chave) { while (condicaoDeEspera) wait(); }

Agora vamos dar uma olhada no seu exemplo. O finalzinho usa this, na classe Calculator, para que a sincronização ocorra em cima da própria variável que for criada do tipo calculator. Ou seja, um notifyAll() por ali, acordará todos os que estiverem usando o calculator para um wait(). É o caso das classes Reader.

Entretanto, certamente essa não é a maneira mais usual de se fazer isso. Primeiro que gerou um código confuso. Simplesmente temos a impressão que this da classe calculator e c da classe reader não se referem ao mesmo objeto.

Em segundo lugar, normalmente teremos classes distintas e a forma que elas controlam seu sincronismo fica encapsulado. Ou seja, você não poderia assumir que a classe Calculator sincroniza a si mesma e, portanto, não seria uma boa idéia fazer o wait() do Reader dessa forma.

Portanto, existem duas maneiras mais usuais de se fazer isso:

  1. Você define uma relação onde a calculadora notifica os readers. Isso subaproveita um pouco o mecanismo do wait, mas gera um código muito mais claro e mais reutilizável;

  2. Você utiliza um objeto de sincronização externo e faz tudo dentro de uma classe mediadora. Isso mantém forte dependência entre as classes, portanto essa técnica geralmente é usada em inner classes privadas e é mais próxima do que você quis fazer.

Deixei um exemplo de cada implementação abaixo.

ViniGodoy

Ok, abaixo vai um código mais usual.

import java.util.HashSet;
import java.util.Set;

/**
 * Temos uma classe reader, que dispara uma Thread,
 * que fica aguardando até que alguém diga o número de linhas a serem lidas.
 * <p>
 * Esse número de linhas será obtido através do parse de um arquivo, que será
 * feito pela classe Calculator.
 * <p>
 * A classe calculator, informa a todos os Readers, quantas linhas devem ser
 * lidas.
 */
public class Sample
{
    static class Reader implements Runnable {
        private int lines = 0;

        /**
         * A thread leitora deve esperar até que existam linhas a serem lidas.
         */
        public synchronized void waitLineNumber() {
            try { 
                while (lines &lt= 0) { //Olha a condição antes do wait aqui.
                    wait();
                }
            }
            catch (InterruptedException e) {
                System.out.println(&quot;Processamento da leitura interrompido&quot;);
            }
        }

        /**
         * Aqui deixamos outras threads definirem quantas linhas serão lidas e
         * acordamos o Reader.
         */
        public synchronized void setLinesToRead(int number) {
            lines = number;
            notifyAll();
        }

        public void run() {
            System.out.println(&quot;Aguardando número de linhas.&quot;);
            waitLineNumber();
            System.out.println(lines + &quot; linhas lidas!&quot;);
        }
    }

    /**
     * Agora vem nossa classe calculadora. Ele realiza o cálculo e avisa uma
     * série de readers.
     */
    static class Calculator implements Runnable {
        private Set&lt;Reader&gt; readers = new HashSet&lt;Reader&gt;();

        public void addReader(Reader reader) {
            readers.add(reader);
        }
        
        public void run() {
            // Parseamos o arquivo (de mentirinha aqui)
            int total = 0;
            for (int i = 0; i &lt 100; i++) {
                total += 1;
            }

            // Informamos aos readers quantas linhas devem ser lidas para que
            // possam fazer o seu trabalho.
            for (Reader r : readers) {
                r.setLinesToRead(total);
            }
        }
    }

    private static void startReader(Calculator c) {
        Reader r = new Reader();
        c.addReader(r);
        new Thread(r).start();
    }
    public static void main(String args[]) {
        Calculator c = new Calculator();
        
        //Iniciamos os três readers
        startReader(c);
        startReader(c);
        startReader(c);
        
        //Iniciamos a calculadora.
        new Thread(c).start();
    }
}

No exemplo acima, fique atento aos seguintes detalhes:

1. O reader sozinho é responsável por sincronizar-se. O código fica muito mais claro, pois o objeto de sincronização (nesse caso this) está explicito em todo lugar;
2. O reader não assume qualquer coisa sobre como a sincronização é feita pela classe Calculator;
3. Foi priorizado a clareza do código, sobretudo sobre as regras de negócio.

ViniGodoy
/**
 * Nesse exemplo, Sample é a classe mediadora. Note que não é nossa intenção
 * usar o Reader nem a Calculadora externamente.
 * <p>
 * A variável lock foi criada para definir nosso objeto de sincronização e para
 * tornar o código mais claro.
 * <p>
 * A variável lines é compartilhada pelas duas threads, por isso seu acesso deve
 * ser sincronizado. Note como há muito mais dependência entre as classes aqui.
 * <p>
 * Esse exemplo é mais próximo do mecanismo que você utilizou.
 */
public class Sample {
    private int[] lock = new int[0];
    private int lines = 0;

    class Reader implements Runnable {
        /**
         * A thread leitora deve esperar até que existam linhas a serem lidas.
         */
        public void waitLineNumber() {
            synchronized (lock) {
                try {
                    while (lines &lt= 0) {
                        lock.wait(); // Será disparado pela calculadora
                    }
                }
                catch (InterruptedException e) {
                    System.out.println(&quot;Processamento da leitura interrompido&quot;);
                }
            }
        }

        public void run() {
            System.out.println(&quot;Aguardando número de linhas.&quot;);
            waitLineNumber();
            System.out.println(lines + &quot; linhas lidas!&quot;);
        }
    }

    /**
     * Agora vem nossa classe calculadora. Ele realiza o cálculo e avisa uma
     * série de readers.
     */
    class Calculator implements Runnable {
        private Set&lt;Reader&gt; readers = new HashSet&lt;Reader&gt;();

        public void addReader(Reader reader) {
            readers.add(reader);
        }

        public void run() {
            // Parseamos o arquivo (de mentirinha aqui)
            int total = 0;
            for (int i = 0; i &lt 100; i++) {
                total += 1;
            }

            // Vamos avisar os readers
            synchronized (lock) {
                // Lines é usado nas duas threads, seu set e get deve ser
                // sincronizado.
                lines = total;
                lock.notifyAll();
            }
        }
    }

    private void startReader(Calculator c) {
        Reader r = new Reader();
        c.addReader(r);
        new Thread(r).start();
    }

    public Sample() {
        Calculator c = new Calculator();

        // Iniciamos os três readers
        startReader(c);
        startReader(c);
        startReader(c);

        // Iniciamos a calculadora.
        new Thread(c).start();
    }

    public static void main(String args[]) {
        new Sample();
    }
}
Criado 14 de janeiro de 2007
Ultima resposta 15 de jan. de 2007
Respostas 3
Participantes 2