No começo do Java, para a programação de eventos assíncronos, utilizávamos a classe Thread e a interface Runnable, que permitiam o desenvolvimento de aplicações paralelas. O problema é que não é possível retornar um valor ao final da execução. Assim, foram adicionadas as classes FutureTaks, Future e Callable, que tem mais ou menos a mesma função das anteriores, mas facilitam bastante o desenvolvimento de aplicações paralelas. Veja:

  • Future: Classe que encapsula uma chamada feita em paralelo, sendo possível cancelar a execução de uma tarefa, descobrir se a execução já terminou com sucesso ou erro, entre outras operações;
  • FutureTask: É uma implementação da interface Future a ser executada numa chamada em paralelo. Além disso, com ela é possível fazer as mesmas verificações que fazemos com a interface;
  • Callable: Interface para a implementação de uma execução em paralelo. É muito parecida com a interface Runnable, mas esta não retorna nenhum valor, enquanto a Callable deve retornar um valor ao final da execução;
  • ExecutorService: Classe para o gerenciamento de execuções em paralelo, já que cria um pool de threads, iniciando e cancelando as execuções. Também é possível cancelar este, evitando assim a criação de novas tarefas.

Para demonstrar na prática como usá-las, esse artigo apresentará alguns programas simples utilizando as principais funcionalidades de cada uma.

Praticando

O primeiro exemplo mostra como implementar uma tarefa em paralelo que apenas gera números aleatórios. Isso foi feito na classe GerarNumeroAleatorio com a interface Callable. O método que deve ser implementado é o call() da interface, que ao final da execução retorna o valor gerado.

No main é criado um pool de threads e uma tarefa utilizando a classe GerarNumeroAleatorio na variável task. Assim, esse processo é enviado para o pool com o método submit() e nessa submissão é retornado um objeto do tipo Future. Com ele espera-se a execução terminar no while, que fica verificando se a thread terminou de executar com o método isDone(): isso acontecerá quando for retornado true. Então o valor aleatório gerado é retornado. Por fim, o valor retornado é impresso no console e o pool de threads é finalizado com o método shutdown(). A Listagem 1 mostra o código dessa aplicação.


package future;
 
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
/**
 * 
 * Classe que cria um pool de threads e cria uma thread do tipo GerarNumeroAleatorio 
 que apenas gera um numero e o retorna para a classe que
 * criou a thread.
 * 
 * @author Eduardo Santana
 *
 */
public class Exemplo1Print {
 
      private static final ExecutorService threadpool = 
      Executors.newFixedThreadPool(3);
 
      public static void main(String args[]) throws InterruptedException, 
      ExecutionException {
            GerarNumeroAleatorio task = new GerarNumeroAleatorio();
            System.out.println("Processando a tarefa ...");
            Future<Integer> future = threadpool.submit(task);
            while (!future.isDone()) {
                  System.out.println("A tarefa ainda não foi processada!");
                  Thread.sleep(1); // sleep for 1 millisecond 
                  before checking again
            }
            System.out.println("Tarefa completa!");
            long factorial = (long) future.get();
            System.out.println("O número gerado foi: " + factorial);
            threadpool.shutdown();
      }
 
      // classe que implementa a interface Callable e retorna um numero aleatorio
      private static class GerarNumeroAleatorio implements Callable<Integer> {
 
            @Override
            public Integer call() {
                  Random rand = new Random();
                  Integer number = rand.nextInt(100);
                  return number;
            }
            
      }
}
Listagem 1. Exemplo utilizando a interface Callable e a classe Future

O resultado da execução do programa é mostrado na Listagem 2, onde é possível verificar que a tarefa não é executa assim que é submetida, então o main não avança enquanto a mesma não é executada. Quando isso acontece, o número gerado é impresso na tela.


Processando a tarefa ...
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada! 
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada! 
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
A tarefa ainda não foi processada!
Tarefa completa!
O número gerado foi: 27
Listagem 2. Execução da Listagem 1

A Listagem 3 mostra um exemplo um pouco maior, onde é calculado o fatorial de um número em uma tarefa que será executada em paralelo. Veja que o método retorna um Long com o valor calculado. O método main dessa classe é parecido com o da Listagem 1.

A tarefa é criada na variável task e é enviada para a execução paralela com o método submit(). Ao terminar é retornado o valor calculado que é impresso na tela.

A classe Fatorial é uma implementação da interface Callable e, por isso, implementa o método call(), que tem por objetivo calcular o fatorial do número passado como parâmetro. Para isso, é feito um comando while que vai multiplicando o valor já calculado por um número e subtraindo 1 deste até que ele chegue em 1.


package future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
 
 
public class Exemplo1 {
 
  private static final ExecutorService threadpool = 
  Executors.newFixedThreadPool(3);

  public static void main(String args[]) throws 
  InterruptedException, ExecutionException {
        
    Fatorial task = new Fatorial(20);
    System.out.println("Enviando a tarefa...");
    Future<Long> future = threadpool.submit(task);
    System.out.println("Task is submitted");
    while (!future.isDone()) {
      System.out.println("Tarefa não terminada ainda...");
      Thread.sleep(1); // espera para tentar novamente
    }
    System.out.println("Tarefa finalizada!");
    long factorial = (long) future.get();
    System.out.println("Fatorial de 10 é: " + factorial);
    threadpool.shutdown();
      
}

private static class Fatorial implements Callable<Long> {
    private final int number;

    public Fatorial(int number) {
          this.number = number;
    }

    @Override
    public Long call() {
      long output = 0;
      try {
            output = factorial(number);
      } catch (InterruptedException ex) {
            Logger.getLogger(Exemplo1.class.getName())
            .log(Level.SEVERE, null, ex);
      }
      return output;
    }

    private long factorial(int number) throws 
    InterruptedException {
      if (number < 0) {
            throw new IllegalArgumentException
            ("Number must be greater than zero");
      }
      long result = 1;
      while (number > 0) {
            result = result * number;
            number--;
      }
      return result;
    }
  }
}
Listagem 3. Programa para calcular o fatorial de um número de forma paralela

Repare que os dois exemplos anteriores tinham apenas uma thread rodando em paralelo.

Na Listagem 4 vemos a execução do código apresentado.


Enviando a tarefa...
Task is submitted
Tarefa não terminada ainda... 
Tarefa não terminada ainda...
Tarefa não terminada ainda...
Tarefa não terminada ainda... 
Tarefa não terminada ainda... 
Tarefa não terminada ainda...
Tarefa não terminada ainda...
Tarefa não terminada ainda...
Tarefa não terminada ainda...
Tarefa finalizada!
Fatorial de 20 é: 2432902008176640000
Listagem 4. Execução da Listagem 3

Vamos aumentar um pouco a complexidade, onde na Listagem 5 são iniciadas três tarefas utilizando também a classe GerarNumeroAleatorio.

Perceba que as três tarefas foram criadas nas variáveis tarefa1, tarefa2 e tarefa3 e foram enviadas para serem executadas com o método submit().

Ao final da execução é feita a soma do retorno das três threads e esse valor é exibido no console.

A execução desse código é apresentada na Listagem 6.


package future;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
public class Exemplo1Soma {
 
  private static final ExecutorService threadpool = 
  Executors.newFixedThreadPool(3);

  public static void main(String args[]) throws 
  InterruptedException, ExecutionException {
        
    GerarNumeroAleatorio tarefa1 = new GerarNumeroAleatorio();
    GerarNumeroAleatorio tarefa2 = new GerarNumeroAleatorio();
    GerarNumeroAleatorio tarefa3 = new GerarNumeroAleatorio();
    
    System.out.println("Processando a tarefa ...");
    Future<Integer> futureT1 = threadpool.submit(tarefa1);
    Future<Integer> futureT2 = threadpool.submit(tarefa2);
    Future<Integer> futureT3 = threadpool.submit(tarefa3);
    
    
    
    while (!futureT1.isDone() && futureT2.isDone() 
    && futureT3.isDone()) {
          System.out.println("As tarefas ainda não foram 
          processadas!");
          Thread.sleep(1); // sleep for 1 millisecond 
          before checking again
    }
    System.out.println("Tarefa completa!");
    long valor = futureT1.get();
    valor = valor + futureT2.get() + futureT3.get();
    System.out.println("A soma dos valores gerados são: 
    " + valor);
    threadpool.shutdown();
  }

  private static class GerarNumeroAleatorio implements 
  Callable<Integer> {

    @Override
    public Integer call() {
      Random rand = new Random();
      Integer number = rand.nextInt(100);
      System.out.println("Valor Gerado: " + number);
      return number;
    }
        
  }
}
Listagem 5. Somando valores gerados por várias Threads

É possível verificar na Listagem 6 que as três tarefas são executadas em paralelo, gerando cada uma um valor individual. Ao final das três execuções os três valores são somados e essa soma é apresentada na tela.


Processando a tarefa ...
As tarefas ainda não foram processadas!;
As tarefas ainda não foram processadas!;
As tarefas ainda não foram processadas!;
As tarefas ainda não foram processadas!; 
As tarefas ainda não foram processadas!;
As tarefas ainda não foram processadas!;
Tarefa completa!
Valor Gerado: 21
Valor Gerado: 24
Valor Gerado: 49
A soma dos valores gerados são: 94
Listagem 6. Execução da Listagem 5

Agora vamos implementar um exemplo um pouco maior. Na Listagem 7 é mostrado um servidor simples que recebe conexões na porta 8000. Ele é iniciado com uma thread onde é criada uma nova tarefa com o método execute. Esta é implementada com a classe VerificaRequisicao, que de 10 em 10 segundos verifica o status de todas as conexões feitas com o servidor, isto é, se ela foi cancela ou se foi terminada com sucesso.

Além disso, é necessário tratar a requisição de cada usuário e para isso foi implementado um processo bem simples, onde o servidor recebe uma mensagem do cliente e a responde com outra mensagem. Para isto utilizamos a classe TrataRequisicao, que recebe como parâmetro um Socket aberto com o cliente que fez a requisição. Foi colocado um Thread.sleep de cinco segundos na Thread que fica analisando as requisições que ainda não terminaram.


package future;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
/**
 * 
 * Implementacao de um servidor bem simples para demonstrar as varias 
 formas de implementar programacao paralela com Java.
 * 
 * @author Eduardo Santana
 *
 */
public class Servidor implements Runnable {
 
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    List<Future> requisicoes = new ArrayList<Future>();

    public static void main(String args[]) throws IOException {
          // cria uma Thread nova eexcutando o servidor
          System.out.println("Servidor no ar");
          new Thread(new Servidor(8000, 3)).run();
    }

    public Servidor(int port, int poolSize) throws IOException {
          serverSocket = new ServerSocket(port);
          pool = Executors.newFixedThreadPool(poolSize);
    }

    public void run() { // run the service
          try {
                //cria uma tarefa para a verificação das requisições
                pool.execute(new VerificaRequisicao(requisicoes));
                while (true) {
                      System.out.println("Nova requisição!");
                      Future req = pool.submit(new TrataRequisicao
                      (serverSocket.accept()));
                      // armazena todas as requisições
                      requisicoes.add(req);
                }
          } catch (IOException ex) {
                pool.shutdown();
          }
    }
}
 
// classe que executa de 10 em 10 segundos e verifica o status das requisições
class VerificaRequisicao implements Runnable {
      private final List<Future> requisicoes;
 
      VerificaRequisicao(List<Future> requisicoes) {
            this.requisicoes = requisicoes;
      }
 
      public void run() {
        while (true) {
              int somaTerminadas = 0;
              int somaCanceladas = 0;
              int somaEmExecucao = 0;
              try {
                    Thread.sleep(10000);
                    for (Future f : requisicoes) {
                          if (f.isDone()) {
                                somaTerminadas++;
                          } else if (f.isCancelled()) {
                                somaCanceladas++;
                          } else if (!f.isDone()) {
                                somaEmExecucao++;
                          }
                    }
                    System.out.println("Terminadas: " + somaTerminadas);
                    System.out.println("Canceladas: " + somaCanceladas);
                    System.out.println("Execução: " + somaEmExecucao);
              } catch (InterruptedException e) {
                    e.printStackTrace();
              }
        }
  }
}
 
// classe que recebe a requisição do cliente e a responde
class TrataRequisicao implements Runnable {
    private final Socket server;

    TrataRequisicao(Socket server) {
          this.server = server;
    }
 
    public void run() {
      try {
            System.out.println("Conectado a: " + server.getRemoteSocketAddress());
            DataInputStream in = new DataInputStream(server.getInputStream());
            System.out.println(in.readUTF());
            DataOutputStream out = new DataOutputStream(server.getOutputStream());
            Thread.sleep(5000);
            out.writeUTF("Sua conexão terminou! Tchau!");
            server.close();
      } catch (SocketTimeoutException s) {
            System.out.println("Socket timed out!");
      } catch (IOException e) {
            e.printStackTrace();
      } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
      }
    }
}
Listagem 7. Servidor com várias Threads

Apenas para verificar o funcionamento do servidor foi implementado um cliente bem simples, conforme mostra a Listagem 8. A conexão é feita no endereço localhost da porta 8000. Depois de conectado, o cliente envia uma mensagem para o servidor e fica esperando a resposta.


package future;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
* 
* Cliente do servidor desenvolvido para testar as classes de execução em paralelo
* 
* @author Eduardo Santana
*
*/
public class Cliente {

  public static void main(String[] args) {
    String serverName = "localhost";
    int port = 8000;
    try {
          System.out.println("Iniciando a conexão!");
          //faz a conexão
          Socket client = new Socket(serverName, port);
          System.out.println("Conectado a: " + client.getRemoteSocketAddress());
          OutputStream outToServer = client.getOutputStream();
          
          // envia a mensagem para o servidor
          DataOutputStream out = new DataOutputStream(outToServer);
          out.writeUTF("Olá Servidor! " + client.getLocalSocketAddress());
          
          // recebe a resposta do servidor
          InputStream inFromServer = client.getInputStream();
          DataInputStream in = new DataInputStream(inFromServer);
          
          //imprime a resposta
          System.out.println("Resposta: " + in.readUTF());
          client.close();
    } catch (IOException e) {
          e.printStackTrace();
    }

  }
}
Listagem 8. Cliente que conecta ao servidor

O resultado da execução do servidor é mostrado na Listagem 9, onde é possível verificar as várias requisições feitas pelo cliente e a execução da Thread que examina os status da conexão. Esse status é sempre atualizado com as condições atuais do servidor. Vemos que na primeira execução dessa tarefa não existia ainda nenhuma requisição, enquanto na segunda existiam quatro execuções ainda pendentes e uma terminada; já na terceira existiam cinco terminadas e três pendentes, e finalmente, na última execução todas as oito requisições já haviam sido respondidas.


Servidor no ar
Nova requisição!
Verifica o status do servidor!
Terminadas: 0
Canceladas: 0
Execução: 0
Nova requisição!
Conectado a: /127.0.0.1:54841
Olá Servidor! /127.0.0.1:54841
Nova requisição!
Conectado a: /127.0.0.1:54842
Olá Servidor! /127.0.0.1:54842
Nova requisição!
Conectado a: /127.0.0.1:54845
Olá Servidor! /127.0.0.1:54845
Nova requisição!
Nova requisição!
Verifica o status do servidor!
Terminadas: 1
Canceladas: 0
Execução: 4
Conectado a: /127.0.0.1:54846
Olá Servidor! /127.0.0.1:54846
Nova requisição!
Conectado a: /127.0.0.1:54848
Olá Servidor! /127.0.0.1:54848
Nova requisição!
Conectado a: /127.0.0.1:54850
Olá Servidor! /127.0.0.1:54850
Nova requisição!
Conectado a: /127.0.0.1:54861
Olá Servidor! /127.0.0.1:54861
Verifica o status do servidor!
Terminadas: 5
Canceladas: 0
Execução: 3
Conectado a: /127.0.0.1:54889
Olá Servidor! /127.0.0.1:54889
Verifica o status do servidor!
Terminadas: 8
Canceladas: 0
Execução: 0
Listagem 9. Execução da Listagem 8

Apenas como curiosidade, a resposta do servidor para o cliente é apenas uma mensagem informando que a conexão está concluída, como mostra o código a seguir:


Iniciando a conexão!
Conectado a: localhost/127.0.0.1:8000
Resposta: Sua conexão terminou! Tchau!

Com a classe FutureTask e as interfaces Callable e Futere a programação de tarefas assíncronas em Java ficou muito mais fácil, evitando algumas “gambiarras” que antes eram necessárias para a implementação desse tipo de aplicação.

Espero que esse artigo seja útil! Até a próxima!