Impasse

O impasse ocorre quando 2 threads ficam impedidos de obter um bloqueio e cada um espera a liberação do bloqueio do outro.

Nenhum deles poderá ser executado até que abram mão do bloqueio, sendo assim esperarão eternamente um pelo outro.

notify() e wait()

Iremos avaliar como os threads interagem para comunicar, entre outras coisas, o status de seus bloqueios. A classe Object possui 3 métodos: notify(), notifyAll() e wait(), que auxiliam os threads a obterem informações sobre o status de um evento importante para eles.

As pilhas são estruturas que utilizam chamadas wait e notify, também. Neste exemplo, são diversos produtores colocando elementos na pilha e vários consumidores retirando elementos da pilha ao mesmo tempo. Partindo-se do pressuposto de que a pilha tem um tamanho, então, no momento em que esse tamanho for alcançado, não se pode colocar mais elementos. Em contrapartida, quando a pilha fica vazia, também não se pode retirar mais nenhum elemento. Pilha vazia implica em wait para consumidor e notify para produtor; já pilha cheia significa wait para o produtor e notify para o consumidor.

Os métodos wait(), notify() e notifyAll() devem ser invocados em um contexto sincronizado. O thread não pode invocar um método wait() ou notify() de um objeto a não ser que obtenha o bloqueio desse objeto.

Agora mostraremos um exemplo de 2 threads que dependem um do outro para dar prosseguimento às suas tarefas e apresentaremos como usar notify() e wait() para fazer com que interajam seguramente, no momento certo.


        package devmedia;
        public class Consumidor {
        public static void main(String args[ ]) {
        Produtor prod = new Produtor();
        prod.start();
        synchronized(prod) {
        try
        {

        System.out.println(“Esperando o produtor produzir!”);
        prod.wait();
        System.out.println(“Já posso consumir!”);
        }
        catch(InterruptedException e) { }
        }

        }

        }
        class Produtor extends Thread {
        public void run() {

        synchronized(this) {

        System.out.println(“Acabei a produção!”);

        notify();

        }

        }

        }
        

Saída:
Esperando o produtor produzir!
Acabei a produção!
Já posso consumir!

A classe Consumidor instancia um thread Produtor. Não existe consumo se não houver produção. Então antes que aconteça o consumo, o Consumidor espera, liberando o bloqueio e chamando o Produtor. Quando o produtor, realmente, produz, ele imprime uma mensagem e notifica o Consumidor. Nesse momento sim, o Consumidor pode continuar seu processo e consumir.

notifyAll()

Na maioria dos casos é preferível notificar todos os threads que estiverem aguardando por um objeto específico. Se isso ocorrer, você pode usar notifyAll() no objeto para permitir que todos os threads saiam do estado de espera e voltem ao estado executável.

Os métodos notifyAll(), notify() e wait() são métodos de java.lang.Object. Prontifique-se a saber quais métodos são definidos em java.lang.Runnable e java.lang.Thread. Dos principais métodos de Thread certifique-se de saber quais são static: yield() e sleep() e quais não são: start() e join().

Mini-teste

  1. Analise o seguinte trecho de código:
    
                    package devmedia;
                    class X { }
                    class Y implements Runnable { public void run() { } }
                    class Z extends Thread { }
                    class Main {
                    public static void main(String args[ ]) {
                    X x = new X();
                    Y y = new Y();
                    Z z = new Z();
                    Thread th1 = new Thread(x); //1
                    Thread th2 = new Thread(y); //2
                    Thread th3 = new Thread(z); //3
                    Thread th4 = new Thread(new Z()); //4
                    Thread th5 = new Thread(); //5
                    }
                    }
                    
    Das instanciações acima, quantas são válidas?
    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. Nenhuma
  2. Analise o seguinte trecho de código:
    
                    package devmedia;
                    class Runner implements Runnable { public void run() { } }
                    public class Executavel {
                    public static void main(String args[ ]) {
                    Runner runner = new Runner();
                    Thread t1 = new Thread(runner);
                    Thread.sleep(10*60*1000);
                    }
                    }
                    
    O que ocorrerá?
    1. Uma exceção será lançada.
    2. O thread t1 será colocado em estado executável.
    3. O programa executará, mas nada será exibido.
    4. O código não compilará.
  3. Analise o seguinte trecho de código:
    
                    try
                    {
                    Thread.sleep(2000);
                    }
                    catch(XXXXXXXXXX) { }
                    
    Qual(is) classe(s) de exceção pode(m) ser colocada(s) no lugar de XXXXXXXXXX ?
    1. InterruptedException
    2. Exception
    3. ArrayIndexOutOfBoundsException
    4. ClassCastException
    5. RuntimeException
  4. Analise o seguinte trecho de código:
    
                    package devmedia;
                    public class Executavel extends Thread {
                    public static void main(String args[ ]) {
                    Thread th1 = new Exemplo();
                    th1.start();
                    Thread th2 = new Executavel();
                    th2.start();
                    th1.start();
                    }
                    public void run() {
                    System.out.println(“A”);
                    }
                    }
                    
    Qual será a saída para esse código?
    1. A
    2. AA
    3. AAA
    4. Uma exceção será lançada.
    5. O código não compilará.
  5. Considere que o método a seguir foi apropriadamente sincronizado e chamado por um thread X no objeto Y:
    
                    wait(2000);
                    
    Após esse método ter sido chamado, quando o thread X estará apto a ter outra chance de ser executado pela CPU?
    1. Dois segundos após o thread X ser notificado.
    2. Dois segundos após o bloqueio de Y ser liberado.
    3. Depois que o thread X for notificado, ou após dois segundos.
    4. Depois que o bloqueio de Y for liberado, ou após dois segundos.
  6. Analise o seguinte trecho de código:
    
                    public class Main{
                    public static synchronized void main(String args[ ]) throws InterruptedException {
                    Thread th = new Thread();
                    th.start();
                    System.out.println(“A”);
                    th.wait(10000);
                    System.out.println(“B”);
                    }
                    }
                    
    Qual será o resultado desse código?
    1. O código não compilará.
    2. Será lançada uma exceção em tempo de execução.
    3. Exibirá A e finalizará.
    4. Exibirá A e nunca finalizará.
    5. Exibirá AB e finalizará quase imediatamente.
    6. Exibirá AB com um atraso de 10 segundos entre A e B.
    7. Exibirá AB com um atraso de 10000 segundos entre A e B.

Gabarito comentado

  1. Resposta correta: D

    É preciso saber que há somente duas maneiras corretas de se instanciar um thread: usando o construtor Thread sem argumentos e utilizando-se o construtor Thread(Runnable r).

    Ao usar o construtor com um argumento Runnable, é preciso passar uma instância Runnable. A classe Y implementa Runnable, então uma instância sua pode ser passada ao construtor Thread(y) sem problemas.

    Quando uma classe estende Thread, ela, de forma automática, possui o método run(). Pode parecer esquisito à primeira vista, mas a classe Thread também implementa Runnable. Desse modo, as linhas com comentários 3 e 4 de fato instanciam threads.

  2. Resposta correta: D

    O método estático sleep(), da classe Thread, lança uma exceção verificada. Apenas para recordarmos, uma exceção verificada obriga o programador a tratá-la por meio de um bloco try/catch ou declará-la por meio de uma cláusula throws.

  3. Resposta correta: A e B

    O método estático sleep() de java.lang.Thread lança uma InterruptedException, que é uma exceção checada/verificada. Dessa forma, esse método deve estar dentro de um bloco try/catch ou o método chamador deve lançar uma InterruptedException.

    Na hierarquia de classes de exceções, InterruptedException é uma subclasse de Exception. Sendo assim, Exception também pode ficar no lugar indicado na questão.

  4. Resposta correta: D

    Uma vez inativo, o thread jamais poderá ser reiniciado. Se houver uma referência a um Thread e invocar start() ele é iniciado. Se for chamado start() mais de uma vez, isso causará uma exceção do tipo IllegalThreadStateException, que é um tipo de RuntimeException.

    Isso acontece independente do método run() ter ou não concluído em relação à primeira chamada à start(). Um novo thread pode ser começado somente uma única vez. Um thread que estiver rodando, ou inativo, não pode ser reiniciado.

    É o agendador de threads que decide qual thread, dos qualificados, será realmente processado. Entenda qualificado como no estado executável.

    Qualquer thread no estado executável pode ser escolhido pelo agendador como o único a ser processado. Um thread que não esteja no estado executável, não poderá ser selecionado como aquele que será executado imediatamente.

  5. Resposta correta: C

    O thread X espera por dois segundos ou por uma chamada notify(). Se o thread X não receber uma chamada notify() em até dois segundos, ele retorna para o pool executável.

  6. Resposta correta: B

    O método main() está sincronizado, mas não em th. Nesse caso, uma IllegalMonitorStateException será lançada.


Leia todos artigos da série