Translate

quinta-feira, 31 de maio de 2012

Java - Threads

Uma thread nada mais é do que a execução de instruções em série para cumprir o objetivo de uma determinada rotina.

Seu termo simplificado, é quando você escreve um programa simples e o executa, gera uma linha de execução (thread) no processador.

Se você escrever duas rotinas distintas e solicitar a execução simultânea destas rotinas, gera duas linhas de execução (duas threads) no processador.

Calma ai, se você se perguntou "como um processador pode executar dois trabalhos ao mesmo tempo se ele é apenas um?", já fez um grande avanço. Mas se você não se fez esta pergunta, considere-a por enquanto.

Para responder a pergunta, é preciso levar em conta dois aspectos dos processadores:

Processador sem suporte para múltiplas threads

Um processador sem suporte para múltiplas threads normalmente é utilizado pelo sistema operacional de forma compartilhada se você solicitar a execução de dois processos ao mesmo tempo. Esta técnica é chamada de time slice, ou seja, simboliza o ato de compartilhamento do tempo do processador para executar instruções de duas ou mais rotinas ativas.

Em termos de desempenho, o time slice não traz nenhuma vantagem, pois o tempo de processamento é dividido entre as rotinas em atividade e quanto mais rotinas em atividade paralela mais inviável torna-se o  programa para um processador sem suporte múlti threads.

Processador com suporte para múltiplas threads

Felizmente vivenciamos um momento propício para a utilização de threads porque os processadores modernos disponíveis na maior parte dos computadores pessoais são capazes de executar em tempo real pelo menos três instruções ao mesmo tempo. A tecnologia de processadores para servidores possui uma capacidade maior do que os processadores pessoais.

Em computadores como notebooks, desktops e servidores todos os processadores oferecem suporte a múltiplas threads o que viabiliza este artigo.

Thread em Java


Há duas formas básicas para executar sua própria thread em Java, a primeira é herdar a classe java.lang.Thread e implementar seu método run(), a segunda forma é implementar a classe java.lang.Runnable e utilizá-la em conjunto com a classe java.lang.Thread.

Utilizando somente java.lang.Thread

Crie a classe MinhaThread conforme o seguinte código:

public class MinhaThread extends Thread {

    private static int numeroInstancias;

    static {
        numeroInstancias = 0;
    }
   
    public MinhaThread(String nomeInstancia) {
        super(nomeInstancia);
        numeroInstancias++;
    }
   
    @Override
    public void run() {
       
        System.out.println("Executando instância [ID: " + super.getId() + ", Nome: " + super.getName() + "] de MinhaThread.");
        System.out.println("Instâncias criadas " + numeroInstancias + ".");

    }
   
}


Agora crie a classe TesteThread para executar instâncias da classe MinhaThread:

public class TesteThread {

    public static void main(String[] args) throws InterruptedException {
       
        MinhaThread t1 = new MinhaThread("ObjetoTh1");

        MinhaThread t2 = new MinhaThread("ObjetoTh2");

        // Abre execução paralela de t1
        t1.start();

        // Abre execução paralela de t2
        t2.start();

        // Aguarda término de t1
        t1.join();

        // Aguarda término de t2
        t2.join();

        // Encerra aplicação (instância JVM)
        System.exit(0);
       
    }
   
}


Ao criar as threads t1 e t2, observe que ao construí-las com o comando new, foram informados nomes distintos para cada instância de MinhaThread.

Ao invocar o método start() de t1, uma nova linha de execução é criada em paralelo a linha de execução de TesteThread iniciada pelo método main(). Outra linha de execução é criada em paralelo ao invocar o método start() de t2.

Agora "O Grande Truque" para realçar o entendimento, veja que para t1 e t2, foi executado o método join(). Isso faz a linha de execução de main() aguardar o término de uma outra linha, se não fosse isso, ao executar o System.exit(0), todas as thread seriam encerradas imediatamente com a aplicação. Faça o teste sem a chamada do join() e observe que t1 ou t2 são interrompidas durante ou antes de suas execuções.

Utilizando java.lang.Thread e java.lang.Runnable

Crie a classe MinhaRotina conforme o seguinte código:

class MinhaRotina implements Runnable {

    private String meuNome;
  
    public MinhaRotina(String nomeInstancia) {
        this.meuNome = nomeInstancia;
    }
  
    @Override
    public void run() {

        System.out.println("Executando a rotina [Nome: " + this.meuNome + "] do tipo MinhaRotina.");

    }
  
}


Crie a classe TesteThread2 para executar instâncias da classe MinhaRotina:

public class TesteThread2 {

    public static void main(String[] args) throws InterruptedException {
       
        MinhaRotina r1 = new MinhaRotina("Rotina1");

        MinhaRotina r2 = new MinhaRotina("Rotina2");

        Thread t1 = new Thread(r1, "Thread da Rotina1");

        Thread t2 = new Thread(r2, "Thread da Rotina2");
       
        // Abre execução paralela de r1
        t1.start();

        // Abre execução paralela de t2
        t2.start();

        // Aguarda término de r1
        t1.join();

        // Aguarda término de r2
        t2.join();

        // Encerra aplicação (instância JVM)
        System.exit(0);
       
    }
   
}


Ao criar as rotinas r1 e r2, observe que ao construí-las com o comando new, foram informados nomes distintos para cada instância de MinhaRotina. Já ao criar t1 e t2, desta vez foram informados dois parâmetros no construtor de Thread, pois o primeiro parâmetro é a instância de uma classe que implementa Runnable e o segundo parâmetro é o nome da thread que deve ser exclusivo.

Ao invocar o método start() de t1, uma nova linha de execução é criada em paralelo a linha de execução de TesteThread2 iniciada pelo método main(). Outra linha de execução é criada em paralelo ao invocar o método start() de t2.

O entendimento de join() é o mesmo já explicado no exemplo anterior. 

Conclusão

Neste artigo abordei os exemplos fundamentais para aplicação de threads em Java, mas este assunto não se resume a isso, porque existe o aspecto de concorrência que deve ser considerado antes de se tomar a decisão de criar uma aplicação multithread.