[Threads] Vários threads em wait() num objeto, mas notifyAll() não funciona em todos?

Olá!

Estou lendo a parte de threads do livro “Complete Java 2 Certification Study Guide” e em certa altura eles propõem um exercício para mostrar que a ordem de notify é aleatória (enunciado anexado no final do tópico).

A idéia básica era de criar 2 classes: a primeira (Rendezvous) possuiria um método sincronizado que simplesmente chamava o wait(); e a segunda (Waiter) seria uma derivada de Thread onde seu método run() chamaria aquele método da primeira.
Dessa forma vários threads Waiter ficariam aguardando o notify de uma única instância de Rendezvous.

Minha implementação ficou assim:

Main.java

[code]public class Main {

public static void main(String[] args) {
	int waiters = args.length > 0 ? Integer.parseInt(args[0]) : 5;		
	Rendezvous rendez = new Rendezvous();
	
	Thread[] threads = new Thread[waiters];
	
	for(int i = 0; i < waiters; i++) {
		threads[i] = new Waiter(rendez);
		threads[i].start();
	}

	synchronized(rendez) {
		rendez.notifyAll();
	} 
	
	System.out.print("---Program terminated---");
}

}[/code]

Rendezvous.java

public class Rendezvous { public synchronized void hurryUpAndWait() { try { wait(); } catch(InterruptedException e) { } } }

Waiter.java

[code]public class Waiter extends Thread {
private static int counter = 0;

private Rendezvous rendez;
private int sn;

public Waiter(Rendezvous rendez) {
	this.rendez = rendez;
	this.sn = counter++;
}

public void run() {
	rendez.hurryUpAndWait();
	System.out.printf("Thread #%d just got notified.\n", sn);
}

}[/code]

Problemas:

  • Ao rodar o programa eu nunca recebia a mensagem “Thread #? just got notified” de todos os threads. Por exemplo:
    — com 5 instâncias de Waiter, geralmente eu só recebia 3 mensagens - de #0, #1 e #2 - e muitas vezes nenhuma mensagem.
    — com 3 instâncias de Waiter, eu recebia no máximo 1 mensagem (#1) e com ainda mais frequência nenhuma.
    — com 10 instâncias, sempre variava entre 4 e 7 mensagens.
  • Ao final da execução, os Threads continuavam rodando, não deixando o programa terminar.

PS: Vale citar que na maioria das vezes os threads que não imprimiam a mensagem eram os primeiros, mas isso também variava.

Agradeço qualquer esclarecimento.

ANEXOS:

Enunciado do exercício:

[quote]Begin with a class called Rendezvous, which has a single method called hurryUpAndWait(). The
method increments a counter and then calls wait(). Does the method need to be synchronized?

Next create a class called Waiter, which extends Thread. Its constructor should take an argument
of type Rendezvous. Its run() method calls hurryUpAndWait() on the instance of Rendezvous and
then prints out a message to report that notification has happened. (Getting notified is the only
way to return from hurryUpAndWait().) Each instance of Waiter should have a unique serial number,
assigned at creation time and printed out after notification, so that you will be able to know
the order in which threads were notified.

Your main class should create one instance of Rendezvous and multiple instances of Waiter.
The number of Waiter instances should be specified on the command line. After each Waiter
is created, call its hurryUpAndWait() method. Eventually all the Waiter instances will be waiting
on the Rendezvous object. Then call notifyAll() on Rendezvous. Observe the order in which
threads report that they have been notified.[/quote]

O negócio é o seguinte. Você deu o notifyAll(), todas as threads foram notificadas.
Porém, só uma conseguirá prosseguir a execução no bloco sincronizado por vez (o que se espera de um bloco sincronizado, anyway).

Se outras tentarem faze-lo enquanto uma estiver percorrendo o bloco, elas voltarão a dormir.

Agora vem a parte interessante. Quantas conseguirão percorrer o bloco? Só deus sabe. O SO acordará a primeira, que pode ou não terminar o seu processamento. Se ela terminar, a segunda terá chance de terminar também. E assim sucessivamente até a última thread.

Agora e se alguém não terminar o processamento e o SO trocar as threads? Bem, a que tentar acordar irá perceber que o bloco não está disponível, e voltará a dormir tranquilamente.

Isso explica o seu número variável de mensagens.
E porque nem todas as threads morrem.

Entendi, só que esse assunto de threads é um tanto novo pra mim.
A razão de eu ter posto o notifyAll() em um bloco sincronizado é que o compilador sempre lançava uma exceção quando eu mandava direto no main, algo relacionado com “A thread (main) não ter a posse do monitor”.

Como, então, eu poderia dar o notifyAll() no rendez de forma que todas a threads que agurdam nele acordem em paralelo?

Não, você não entendeu.

Quando vc dá o notifyAll(), todas é dado o comando para acordar em todas, e isso está certo.

O que eu disse ali é que, no momento que elas acordam, pode ocorrer de numa troca de contexto uma thread voltar a dormir, pq outra thread acordada já recém adquiriu o controle do trecho sincronizado.

Sim eu entendi que todas acordam, o que eu ainda não descobri é como fazer pra que todas acordem e de fato continuem em “running”.
Evidentemente eu terei que retirar aquele bloco sincronizado, mas sem ele como seria possível dar o notifyAll() em rendez?

Não, você só pode dar um notifyAll() se tiver o controle do monitor, isto é, dentro de um bloco sincronizado.

Para todas continuarem rodando, você teria que continuar notificando as threads. Na verdade, se o seu intuito é esse, é melhor usar uma classe especial para isso chamada CyclicBarrier, do pacote java.util.concurrent.

Só por curiosidade, também veja a classe CountDownLatch, também usada para sincronizações desse tipo.

Mas se essas são realmente as únicas alternativas, qual seria a utilidade de notifyAll()? Sabendo que existe a possibilidade de alguma thread não rodar.

Quando o notifyAll() é chamado uma thread aleatória adquire o lock, e todas as restante ficam bloquadas esperando que a anterior libere o lock, dessa forma todas não deveriam rodar sequencialmente (mas não em ordem)? E sempre que uma libere o lock esta não seria finalizada?