Wait, notify e notifyall

Olá pessoal,

Estou estudando para a certificação Java e estou na parte sobre Threads mas estou com dificuldades em entender a parte sobre wait, notify e notifyall. Tenho duas dúvidas.

1 dúvida é referente a esse código:

package waitNotify;

public class Leitor extends Thread {

    Calculator c;

    public Leitor(Calculator calc){
        c = calc;
    }

    public void run(){
        synchronized(c){
            try{
                System.out.println("Aguardando a finalização do cálculo...");
                c.wait();
            } catch(InterruptedException e){ }
        }
        System.out.println("O total é: " + c.total);
    }

    public static void main(String[] args) {
        Calculator calculo = new Calculator();
        new Leitor(calculo).start();
        new Leitor(calculo).start();
        new Leitor(calculo).start();
        calculo.start();
    }

}
package waitNotify;

public class Calculator extends Thread {
    int total;
    public void run(){
        synchronized(this){
            for(int i = 0; i<100; i++){
                total += 1;
                notifyAll();
            }
        }
    }
}

A saída é:

Aguardando a finalização do cálculo…
Aguardando a finalização do cálculo…
Aguardando a finalização do cálculo…
O total é: 100
O total é: 100
O total é: 100

Vamos a minha dúvida… no método main é criado um objeto Calculator(que é uma thread) e esse objeto é passado como argumento para 3 instâncias de Leitor(que também são threads). Pelo que eu havia lido, quando chamo uma thread e passo um objeto no argumento seria chamado o método run desse objeto passado como argumento(ele é o destino, se fosse um Runnable poderia ser chamado de Runnable de destino), correto?
Então quando eu crio as 3 instâncias de Leitor não deveria chamar 3 vezes o método run que faz o cálculo do total? Pq então parece que chamou 3 vezes o método run de Leitor(aparecem 3 mensagens ‘Aguardando a finalização do cálculo…’) ? Porque entra primeiro no método run de Leitor ao invés de entrar no run de Calculator já que o objeto Calculator foi passado 3 vezes como argumento?

Explicando melhor minha dúvida, pelo que eu entendi seria isso:

    new Leitor(calculo).start(); //deveria chamar run de Calculator
    new Leitor(calculo).start(); //deveria chamar run de Calculator
    new Leitor(calculo).start(); //deveria chamar run de Calculator
    calculo.start(); //deveria chamar run de Calculator

Não entendo como e porque o método run de Leitor foi chamado.

A dúvida 2 tem a ver com outro código:

package waitNotify;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Controlador extends JFrame {
	private JButton btnPausa = null;
	private JScrollPane scrlTexto = new JScrollPane();
	private JTextArea txtArea = new JTextArea();
	private Impressora impressora;
	
	public Controlador() {
		super("Demonstração do wait e notify");
		
		setLayout(new BorderLayout());
		add(getBtnPausa(), BorderLayout.NORTH);
		txtArea.setEditable(false);
		scrlTexto.add(txtArea);
		scrlTexto.setViewportView(txtArea);
		add(scrlTexto, BorderLayout.CENTER);
		setSize(640,480);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		impressora = new Impressora(txtArea);
	}

	private JButton getBtnPausa() {
		if (btnPausa == null) {
			btnPausa = new JButton("Pausa");
			btnPausa.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					if (btnPausa.getText().equals("Pausa"))
					{
						btnPausa.setText("Continua");
						impressora.setPausado(true);
						return;
					}
					
					btnPausa.setText("Pausa");
					impressora.setPausado(false);
				}
			});
		}
		return btnPausa;
	}
	
	public static void main(String args[]) {
		new Controlador().setVisible(true);
	}
}
package waitNotify;

import javax.swing.JTextArea;

public class Impressora {
	private JTextArea txtDestino = null;

	private long linha = 0;

	private boolean pausado = false;

	public Impressora(JTextArea txtDestino) {
		if (txtDestino == null)
			throw new NullPointerException("Destino não pode ser nulo!");

		this.txtDestino = txtDestino;

		//Disparamos a thread da impressora.
		Thread t = new Thread(new ImpressoraRun(), "Thread da impressora");
		t.setDaemon(true);
		t.start();
	}

	private synchronized void verificaPausa() throws InterruptedException {
		while (pausado) {
			wait();
		}
	}

	public synchronized void setPausado(boolean pausado) {
		this.pausado = pausado;

		if (!this.pausado)
			notifyAll();
	}
	
	private void imprime()
	{
		StringBuilder msg = new StringBuilder("Linha ");
		msg.append(Long.toString(linha++ % Long.MAX_VALUE));
		msg.append("\n");
		txtDestino.append(msg.toString());
	}

	private class ImpressoraRun implements Runnable {
		public void run() {
			try {
				while (true) {					
					verificaPausa(); 
					imprime();       
					Thread.sleep(500);
				}
			} catch (InterruptedException e) {
				txtDestino.append("Processamento da impressora interrompido.");
			}
		}
	}
}

Dúvida:

Aqui é disparada a Thread
Thread t = new Thread(new ImpressoraRun(), “Thread da impressora”);
t.setDaemon(true);
t.start();
Então vai rodar o run da classe ImpressoraRun que vai chamar o método verificaPausa onde tem um wait(). Se eu clicar no botão da interface gráfica é chamado o método setPausado onde tem um notifyAll().

Minha dúvida é a seguinte, se tenho duas threads rodando(a do main que é a principal do programa e a que eu disparei que é a da ImpressoraRun) quando é chamado wait e notifyAll como o Java sabe que tem que pausar ou notificar a thread ImpressoraRun e não a thread principal. E se eu tivesse 10 threads por exemplo como o Java saberia qual pausar e qual notificar?

Desculpem o post grande, mas essas 2 dúvidas estão me matando. E não sei se ficaram claras as dúvidas mas qualquer coisa eu explico melhor se alguém não entender o que escrevi.

Obrigado pessoal, abraço.

Esse código é um exemplo de uma série de más práticas em um lugar só.

Não. Calculator é um objeto que dispara threads. Mas ele não é uma thread. Não confunda a thread (que é uma linha de execução) com a classe Thread, que é o disparador da thread.

Não nesse caso. O constutor de Leitor não está passando o Calculator em seu super, portanto, o Calculator não vai ser usado como argumento para a classe Thread.
Ainda que estivesse, Leitor sobrescreve o método run(). Por padrão, o método run() da classe Thread é que faz a chamada ao método run() do Runnable que chega no parâmetro do construtor.

[quote]Então vai rodar o run da classe ImpressoraRun que vai chamar o método verificaPausa onde tem um wait(). Se eu clicar no botão da interface gráfica é chamado o método setPausado onde tem um notifyAll().

Minha dúvida é a seguinte, se tenho duas threads rodando(a do main que é a principal do programa e a que eu disparei que é a da ImpressoraRun) quando é chamado wait e notifyAll como o Java sabe que tem que pausar ou notificar a thread ImpressoraRun e não a thread principal. E se eu tivesse 10 threads por exemplo como o Java saberia qual pausar e qual notificar? [/quote]

Os métodos wait() e sleep() atuam sobre a thread em execução no momento, que os chamou. A thread entra num bloco sincronizado e se associa ao monitor (o objeto que está sendo passado ao synchronized). O notify() e o notifyAll() atuam sobre todas as threads que compartilharem o mesmo monitor.

Por exemplo, no código abaixo, do Calculator, o objeto this é usado como monitor:

É equivalente a só deixar o método como synchronized. Note que o Calculator usa this como monitor, e as threads do leitor usam o mesmo monitor (a variável c, que também é o Calculator). Por isso, um notifyAll() ali irá disparar todas as threads que estiverem em wait() com esse monitor.

Só esclarecendo as más práticas:
a) Estender Thread ao invés de implementar Runnable: Normalmente, nós devemos deixar claro o mecanismo de disparo da thread (Thread) da task a ser executada (Runnable). Isso simplifica o entendimento do código.
b) Usar this como lock, ao mesmo tempo que a mesma variável é usada de maneira externa no código (c no Leitor). Isso torna o entendimento confuso.
c) Não tratar o wait() adequadamente - o que torna o código sujeito a spurious wakeups;
d) Passar como parâmetro um Runnable no construtor de Thread ao mesmo tempo que o método run() é sobrescrito. Esse tipo de porquice não só tornou o código confuso para você, como também é dependente de implementação, uma vez que o Javadoc da classe thread não especifica o que acontece nessa situação (o famoso “undefined behavior”).