A robusta plataforma Java nos oferece mais um bônus quando queremos tratar de processamento paralelo. Neste artigo apresentaremos de forma prática o uso de Threads em Java, com o objetivo de trabalhar de forma paralela, também conhecida por programação concorrente.

Threads

Antes de iniciarmos a explicação prática de Thread, entenda que estas são subdivisões dos processos, como assim? Cada processo possui diversas threads (linhas de instruções), assim nós podemos dividir partes do nosso processo (programa em Java) para trabalhar paralelamente.

As threads estão em nosso dia-a-dia, em todos os processamentos realizados em nosso computador. Quando estamos ouvindo uma música e olhando o Facebook ao mesmo tempo, estamos realizando um processamento paralelo, mesmo que de forma transparente ao usuário.

Em um programa em Java podemos querer executar 2 ou mais threads ao mesmo tempo, ou seja, 2 ou mais procedimentos internos do programa ao mesmo tempo. Para simplificar a explicação de threads em Java, vamos a um exemplo prático.

Imagine que você tem 1 procedimento que consome muito tempo do processador, vamos supor o exemplo abaixo de um cálculo que faz consultas a um Web Service (que pode demorar muito a responder):

public void calculaTotalRecebido(){
    //Recebe aproximadamente 70mil registros. 
    List<HistoricoRecebimento> recebidos = getListRecebimentos();
    Integer soma = 0;
    
    for(HistoricoRecebimento h1: recebidos){
      soma = soma + recebidos.getValorRecebido();
    }
    
    Integer porcentagemImposto = getReajusteAtualFromWebService();
    
    soma = soma + ((porcentagemImposto/100)*soma);
    
    retornaParaWebServiceValorTotal(soma);
  }
Listagem 1. Procedimento comum sem thread

O procedimento acima parece simples (de fato é simples), recebe uma lista com mais ou menos 70 mil registros, depois pega o valor de cada registro e soma. Após isso, captura um valor para reajuste em porcentagem (capturado de um WebService que pode demorar a responder), e recalcula o valor somado. Por fim retorna o resultado via WebService.

Perceba aqui claramente que temos um procedimento que pode demorar minutos, e não vai retornar nada ao usuário, apenas fazer comunicação interna entre Web Services. Não podemos parar toda nossa aplicação para executar o procedimento acima. Pois imagina se o usuário está fazendo um cadastro simples e tem que esperar 4 minutos para terminar o processamento acima.

A solução então é fazer com que esse procedimento seja executado concorrentemente, ou seja, ao mesmo tempo em que o usuário está realizando o cadastro, o procedimento acima também é executado, e provavelmente quando ele terminar o cadastro, o procedimento acima também já terminou, de forma imperceptível a ele.

No código abaixo vamos usar o mesmo código, porém usando o conceito de Threads. Iremos criar uma Thread para um bloco específico de código, através da Classe java.lang.Thread.

Existe uma interface chamada Runnable que possui um método run. Dentro do método run devem ficar os procedimentos que você deseja executar paralelamente, sendo assim vamos colocar todo o código acima dentro de um método run. Como Runnable é apenas um contrato, precisamos de alguma classe que a implemente e faça o trabalho da “paralelização”, que é a classe Thread.

  public void calculaTotalRecebido(){
    new Thread() {
      
      @Override
      public void run() {
        //Recebe aproximadamente 70mil registros. 
        List<HistoricoRecebimento> recebidos = getListRecebimentos();
        Integer soma = 0;
        
        for(HistoricoRecebimento h1: recebidos){
          soma = soma + recebidos.getValorRecebido();
        }
        
        Integer porcentagemImposto = getReajusteAtualFromWebService();
        
        soma = soma + ((porcentagemImposto/100)*soma);
        
        retornaParaWebServiceValorTotal(soma);
        
      }
    }.start();

  }
Listagem 2. Usando Thread

Quando fazemos o “.start();” já estamos iniciando o processamento paralelo, e liberando o programa para executar qualquer outra thread. Então tenha a seguinte idéia assimilada: Se você deseja que o programa não “trave para o usuário” naquele determinado procedimento que pode demorar muito tempo, use Thread.

Caso você deseja que sua classe seja processada paralelamente, porém ela já estende de outra, você poderá optar por implementar o Runnable, que é a interface padrão para Thread. Por boas práticas, geralmente implementamos Runnable em vez de estender de Thread.

Atente a outro ponto muito importante: O programador não tem nenhum controle sobre o escalonador do processador. Isso significa que sendo os processos executados paralelamente, você não tem como saber qual a ordem de execução destes. No exemplo abaixo vamos colocar 2 contadores para executar paralelamente, você irá perceber que o resultado pode diferir cada vez que você executar, ou até de computador para computador.

  public class MyTest {
      static int i = 0;
      public static void main(String[] args) {
          new Thread(t1).start();
          new Thread(t2).start();
      }

      private static void countMe(String name){
          i++;
          System.out.println("Current Counter is: " + i + ", updated by: " + name);
      }

      private static Runnable t1 = new Runnable() {
          public void run() {
              try{
                  for(int i=0; i<5; i++){
                      countMe("t1");
                  }
              } catch (Exception e){}

          }
      };

      private static Runnable t2 = new Runnable() {
          public void run() {
              try{
                  for(int i=0; i<5; i++){
                      countMe("t2");
                  }
              } catch (Exception e){}
         }
      };
  }
Listagem 3. Teste de Contadores com Thread

Vou deixar por conta sua testar o código acima e ver qual o resultado, pois cada computador pode ter um ordem de execução diferente, isso é comum e muito normal.

Conclusão

Utilize os recursos da programação concorrente com cuidado e só nos momentos que de fato precisar. Não saia de forma alguma colocando Runnable em todos os códigos pensando que seu programa ficará mais rápido, pelo contrário, um processo pode precisar de recursos de outro que ainda não está disponível e assim por diante, analise com cuidado a necessidade do uso deste poderoso recurso.

Um exemplo mais comum ainda de ser visto é a barra de progresso presente na maioria dos softwares. Enquanto está carregando um documento X é mostrada uma barra de progresso para o usuário saber que o software não travou.

Links Úteis